如何判断一个窗口句柄是否为桌面顶层窗口
在微软官方文档中,并没有提供一个 API 用于判断一个窗口句柄是否为桌面的顶层窗口。通过类似 NULL == GetParent(hwnd)
或者 hwnd == GetAncestor(hWnd,GA_ROOT)
等简单代码都无法做出正确判断。经过测试,找到两种靠谱的解决方案。
方法一
在 Windows 7 及以上版本的 Windows 中,User32.DLL 导出了一个名为 IsTopLevelWindow()
的 Undocumented API,其函数原型如下:
c++BOOL WINAPI IsTopLevelWindow (
_In_ HWND hWnd
);
方法二
用 EnumWindows()
枚举所有桌面窗口,然后和目标窗口句柄进行匹配。
完整实现代码
结合两种方法,实现一个兼容的 IsTopLevelWindow()
函数:
c++#define USER_DEFINED_ERROR_CODE 0x10000000
#define TOP_LEVEL_WINDOW_ERROR (USER_DEFINED_ERROR_CODE | 4389)
typedef BOOL(WINAPI *IsTopLevelWindowProc)(HWND hwnd);
IsTopLevelWindowProc IsTopLevelWindow = NULL;
static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) {
if (hwnd == reinterpret_cast<HWND>(lParam)) {
SetLastError(TOP_LEVEL_WINDOW_ERROR);
return FALSE;
}
return TRUE;
}
static BOOL WINAPI _IsTopLevelWindow(HWND hwnd) {
if (!EnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(hwnd)) && GetLastError() == TOP_LEVEL_WINDOW_ERROR) {
return TRUE;
}
return FALSE;
}
void InitIsTopLevelWindow()
{
HMODULE user32Dll = GetModuleHandle(_T("user32.dll"));
if (user32Dll) {
IsTopLevelWindow = (IsTopLevelWindowProc)GetProcAddress(user32Dll, "IsTopLevelWindow");
}
if (NULL == isTopLevelWindow) {
IsTopLevelWindow = _IsTopLevelWindow;
}
}
代码尝试从 User32.DLL 中获取 IsTopLevelWindow()
的 API 入口,如果失败则使用自定义的 _IsTopLevelWindow()
函数替代。
经过一番搜索,发现还有其他几种方案。一并附在文中。
方法三
此方案只列出可见窗口。如果列出隐藏窗口,可以对代码稍做修改。
c++BOOL IsAltTabWindow(HWND hwnd)
{
// Start at the root owner
HWND hwndWalk = GetAncestor(hwnd, GA_ROOTOWNER);
// See if we are the last active visible popup
HWND hwndTry;
while ((hwndTry = GetLastActivePopup(hwndWalk)) != hwndTry) {
if (IsWindowVisible(hwndTry)) break;
hwndWalk = hwndTry;
}
return hwndWalk == hwnd;
}
代码出处文章为:Which windows appear in the Alt+Tab list?
方法四
这个方法出自 Stack Overflow 的一个主题:Show a list of all “Alt+Tab windows” (even full screen UWP windows) and retrieve the handle of the one picked by the user。这个问题中主要探讨了如何获取包含全屏 UWP 窗口在内的 Alt+Tab 窗口列表。原始代码是 C#,此处转译为 C++ 代码:
c++BOOL IsAltTabWindow(HWND hwnd)
{
// 如果要列出隐藏窗口,注释掉下面这行
if (!IsWindowVisible(hWnd)) return FALSE;
if (GetAncestor(hwnd, GA_ROOTOWNER) != hwnd) return FALSE;
DWORD cloaked = 0;
// 注意:Windows 7 及更早版本不支持 DWMWA_CLOAKED
DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloaked, sizeof(cloaked));
if (cloaked == DWM_CLOAKED_SHELL) return FALSE;
auto style = GetWindowLong(hwnd, GWL_EXSTYLE);
if ((style & WS_EX_TOOLWINDOW) != 0) return FALSE;
return TRUE;
}
除此之外,还在微软官方文档中找到一个名为 GetAltTabInfo 的 API 函数。不过经测试似乎没什么作用,无论传入什么窗口句柄都返回 FALSE
。