DPI变更后如何正确更新托盘图标?
托盘图标在任务栏DPI变更时模糊的解决方案
问题描述
我用以下标准C++代码创建托盘图标:
NOTIFYICONDATA data; // ... data.hIcon = (HICON)LoadImage(inst, L"name", IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR); // ... Shell_NotifyIcon(NIM_ADD, &data);
但当任务栏所在显示器的DPI变更时,图标会被拉伸变得模糊,如图所示:
网上建议用WM_DPICHANGED消息处理,但根据文档:
当窗口的有效每英寸点数(dpi)发生变化时发送此消息。DPI是窗口的缩放因子,多种事件会导致DPI变化,包括:
- 窗口被移动到DPI不同的新显示器;
- 窗口所在显示器的DPI发生变化。
这意味着如果主显示器DPI变更,但我的窗口在另一台显示器上,就收不到该消息。我试过创建隐藏窗口并固定在主显示器,但不够优雅;也考虑过定时器轮询,但同样不理想。请问有没有始终有效的推荐解决方案?
推荐解决方案
1. 标记应用为高DPI感知
首先确保你的应用被系统识别为高DPI感知,避免系统自动缩放图标。可以通过以下两种方式实现:
- 添加应用清单(Manifest):在清单中加入高DPI感知声明:
<application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness> </windowsSettings> </application> - 代码设置:在程序启动时调用
SetProcessDpiAwarenessContext设置感知模式:SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
2. 根据任务栏所在显示器的DPI加载图标
不要直接用SM_CXSMICON固定尺寸加载图标,而是获取任务栏所在显示器的DPI,计算对应图标尺寸:
// 获取任务栏所在的显示器句柄 HWND hTaskbar = FindWindow(L"Shell_TrayWnd", nullptr); HMONITOR hMonitor = MonitorFromWindow(hTaskbar, MONITOR_DEFAULTTOPRIMARY); // 获取显示器的DPI UINT dpiX, dpiY; GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); // 计算图标尺寸:系统默认小图标尺寸是16x16(96DPI),按DPI比例缩放 int iconSize = MulDiv(16, dpiX, 96); // 加载对应尺寸的图标 data.hIcon = (HICON)LoadImage(inst, L"name", IMAGE_ICON, iconSize, iconSize, LR_DEFAULTCOLOR | LR_LOADFROMFILE);
3. 监听全局DPI变更通知
使用WM_SETTINGCHANGE消息代替WM_DPICHANGED,该消息会在系统DPI设置变更时广播给所有窗口,不受窗口所在显示器限制:
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_SETTINGCHANGE: if (wParam == SPI_SETNONCLIENTMETRICS) { // DPI设置变更,更新托盘图标 UpdateTrayIcon(hWnd); } break; // 处理TaskbarCreated消息,防止资源管理器重启后图标消失 case WM_TASKBARCREATED: Shell_NotifyIcon(NIM_ADD, &data); break; // ...其他消息处理 } return DefWindowProc(hWnd, msg, wParam, lParam); }
4. 更新托盘图标
当需要更新图标时,调用Shell_NotifyIcon(NIM_MODIFY, &data)替换新的图标,无需重新添加。
这样无论任务栏在哪个显示器,只要DPI变更,你的程序都能收到通知并加载对应尺寸的图标,避免模糊问题。
内容的提问来源于stack exchange,提问作者Paul
相关产品推荐
相关产品推荐

