如何判断一个窗口句柄是否为桌面顶层窗口

在微软官方文档中,并没有提供一个 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