如何在Windows 10中通过WinAPI正确获取通知窗口句柄?
嘿,这个多语言适配的坑我之前也踩过——靠硬编码不同语言的窗口标题确实太蠢了,不仅维护起来要命,Windows一更新说不定就失效了。给你几个更靠谱的方案,彻底避开标题依赖:
方案1:用窗口类名替代标题判断
ShellExperienceHost的通知气泡窗口有固定的类名ToastNotificationWindow,这个名称不会随系统语言变化,比标题靠谱得多。步骤如下:
- 先获取
ShellExperienceHost.exe的进程ID(PID) - 枚举所有窗口,筛选出属于该PID的窗口
- 对这些窗口获取类名,匹配
ToastNotificationWindow即可找到目标窗口
给你一段C++示例代码:
#include <windows.h> #include <tlhelp32.h> #include <wchar.h> DWORD GetShellExperienceHostPID() { PROCESSENTRY32 pe32; pe32.dwSize = sizeof(PROCESSENTRY32); HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot == INVALID_HANDLE_VALUE) return 0; DWORD pid = 0; if (Process32First(hSnapshot, &pe32)) { do { if (_wcsicmp(pe32.szExeFile, L"ShellExperienceHost.exe") == 0) { pid = pe32.th32ProcessID; break; } } while (Process32Next(hSnapshot, &pe32)); } CloseHandle(hSnapshot); return pid; } BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) { DWORD windowPid; GetWindowThreadProcessId(hwnd, &windowPid); DWORD* data = (DWORD*)lParam; DWORD targetPid = data[0]; HWND* resultHwnd = (HWND*)&data[1]; if (windowPid != targetPid) return TRUE; WCHAR className[256]; GetClassName(hwnd, className, 256); if (_wcsicmp(className, L"ToastNotificationWindow") == 0) { *resultHwnd = hwnd; return FALSE; // 找到目标后停止枚举 } return TRUE; } HWND FindToastNotificationWindow() { DWORD sehPid = GetShellExperienceHostPID(); if (sehPid == 0) return NULL; DWORD data[2] = {sehPid, 0}; EnumWindows(EnumWindowsProc, (LPARAM)data); return (HWND)data[1]; }
方案2:使用UI Automation框架(更稳定)
如果要更稳妥,推荐用微软官方的UI Automation框架——这是专门用来自动化UWP/现代UI应用的工具,不需要依赖窗口句柄枚举,还能直接获取通知的内容等信息。
给你一段C#的示例代码(C++也有对应的API,思路类似):
using System; using System.Diagnostics; using System.Windows.Automation; public static AutomationElement LocateToastNotification() { // 从桌面根元素开始遍历 AutomationElement desktopRoot = AutomationElement.RootElement; // 筛选所有窗口类型的元素 var windowCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window); var allWindows = desktopRoot.FindAll(TreeScope.Children, windowCondition); foreach (AutomationElement window in allWindows) { // 获取窗口所属进程ID if (window.GetCurrentPropertyValue(AutomationElement.ProcessIdProperty) is int pid) { try { var process = Process.GetProcessById(pid); // 确认是ShellExperienceHost进程 if (process.ProcessName.Equals("ShellExperienceHost", StringComparison.OrdinalIgnoreCase)) { // 检查窗口类名是否为通知窗口 if (window.GetCurrentPropertyValue(AutomationElement.ClassNameProperty) is string className && className.Equals("ToastNotificationWindow", StringComparison.OrdinalIgnoreCase)) { return window; } } } catch (ArgumentException) { // 进程可能已经退出,跳过 continue; } } } return null; }
注意事项
- 关于权限:普通用户权限足够,不需要管理员权限
- 版本兼容性:
ToastNotificationWindow这个类名在Windows 10和Windows 11上都是稳定的,暂时没有发现版本差异 - 如果是Windows 8及更早版本,类名可能不同,但现在应该很少有人用了,不用太担心
内容的提问来源于stack exchange,提问作者Zbigniew Rębacz
相关产品推荐
相关产品推荐

