在 Windows 中添加系统托盘图标时,我们可以Shell_NotifyIcon()
通过NOTIFYICONDATA
结构传递两个版本的 API。这两个 API 之间有细微的差别,这些都没有在 MSDN 上的任何地方列出。我花了一些精力来找出其中的一些差异,我现在将分享这些差异。总是欢迎对答案进行改进/补充。
PS:这个问题纯粹是为了分享我在过去几天尝试Windows DPI缩放所学到的东西。
在 Windows 中添加系统托盘图标时,我们可以Shell_NotifyIcon()
通过NOTIFYICONDATA
结构传递两个版本的 API。这两个 API 之间有细微的差别,这些都没有在 MSDN 上的任何地方列出。我花了一些精力来找出其中的一些差异,我现在将分享这些差异。总是欢迎对答案进行改进/补充。
PS:这个问题纯粹是为了分享我在过去几天尝试Windows DPI缩放所学到的东西。
uVersion
该NOTIFYICONDATA
结构的成员可以有 3 个可能的值,代表用于创建任务栏图标的 API 版本。
对于托盘图标的消息处理程序wParam
, 和uParam
具有如下图所示的差异。
请注意,在NOTIFYICON_VERSION_4
wParam 中给出了各种事件的 X 和 Y 坐标,但没有提供获取坐标的规定NOTIFYICON_VERSION
。这引起了一个有趣的行为(这是我试图解决的一个 BUG 的原因)。如果您使用NOTIFYICON_VERSION
, 然后调用托盘图标的上下文菜单,那么鼠标光标(无论您在调用菜单时可能在哪里)都会被放置在托盘图标的中心。即使您使用键盘(WINDOWS + B)调用图标的上下文菜单,鼠标光标仍会移动到图标上。
在您查看我试图在Pico torrent应用程序中解决的这个特定 BUG 之前,您可能不会特别感兴趣。
这是场景。
请参阅以下图像以了解正在发生的事情。
问题是,虽然MSDN这么说GET_X_LPARAM(wParam)
,并且GET_Y_LPARAM(wParam)
应该在托盘图标的处理程序中给出正确的值,但是在存在 DPI 缩放的情况下(即,在没有注销和登录的情况下更改 DPI 缩放) . 另一方面,APIGetCursorPos()
返回鼠标光标坐标的正确值。请注意,NOTIFYICON_VERSION_4
withGetCursorPos()
不起作用,因为可以使用键盘调用上下文菜单,鼠标光标可以位于屏幕上的任何位置。
那么,当以上述方式完成 DPI 缩放时,如何结合刚刚学到的所有知识正确显示托盘图标的上下文菜单,而不使您的应用程序感知每个监视器 DPI(对于每监视器 DPI 感知应用程序GET_X_LPARAM(wParam)
,并GET_Y_LPARAM(wParam)
始终返回正确的值)?
使用NOTIFYICON_VERSION
代替NOTIFYICON_VERSION_4
,这将在调用上下文菜单时将鼠标光标定位在托盘图标上,然后用于GetCursorPos()
获取鼠标光标的位置。TrackPopupMenu()
使用坐标显示上下文菜单。
PS:在上面的示例中,DPI 缩放值从 150% 更改为 125%。当您的托盘图标区域位于屏幕的右下方时,当 DPI 从较大值缩放到较小值时,上下文菜单偏差更加明显。这是因为当 DPI 缩放完成时,Windows 使用 DPI 虚拟化放大了每个显示器不感知的 UI 元素,然后事情向右和向下移动。例如。如果在应用程序中一个窗口矩形是(0,0,100,100)(屏幕坐标),那么在放大到 150% 后,它可能变成(0,0,150,150)。现在对于托盘图标的菜单,如果您指定超出屏幕右下角的坐标,则操作系统仍将显示在屏幕内的右下角位置,并确保菜单正确显示。例如。如果屏幕是 1920x1080,并且TrackPopupMenu()
给定菜单 (10000,10000),菜单仍将显示在 1920x1080 屏幕矩形内。因此,如果上下文菜单已经到达最右下角的位置,则增加 DPI 缩放将不会进一步移动上下文菜单。
@sahil-singh 你是对的,我同意其他人的观点,即你的应用程序应该是 DPI 感知的,但是这不是重点。
我有一个类似的问题,我的应用程序(仍然)不支持 DPI 并且 GET_X_LPARAM(wParam) 将返回非虚拟坐标。将此值传递给 TrackPopupMenu() 后,我在屏幕上的位置错误。
最好的方法是使用 GetMessagePos() 而不是 wParam。在这种情况下,Windows 将为您提供带有虚拟坐标的新 DWORD,然后使用 GET_X_LPARAM/GET_Y_LPARAM 获取可以传递给 TrackPopupMenu() 的值。
这似乎(2 年后的 2019 年)记录在 MSDN 上:
NOTIFYICONDATAA 结构体
uCallbackMessage
类型:UINT
当uVersion成员为 0 或 NOTIFYICON_VERSION 时,消息的wParam参数包含发生事件的任务栏图标的标识符。该标识符的长度可以是 32 位。lParam参数保存与事件相关的鼠标或键盘消息。例如,当指针移到任务栏图标上时,lParam设置为WM_MOUSEMOVE。
当uVersion成员为 NOTIFYICON_VERSION_4 时,应用程序继续通过uCallbackMessage成员以应用程序定义的消息的形式接收通知事件,但对该消息的lParam和wParam参数的解释发生了如下变化:
- LOWORD(lParam)包含通知事件,例如 NIN_BALLOONSHOW、NIN_POPUPOPEN 或 WM_CONTEXTMENU。
- HIWORD(lParam)包含图标 ID。图标 ID 的长度限制为 16 位。
- GET_X_LPARAM(wParam)返回通知事件 NIN_POPUPOPEN、NIN_SELECT、NIN_KEYSELECT 以及 WM_MOUSEFIRST 和 WM_MOUSELAST 之间的所有鼠标消息的 X 锚点坐标。如果这些消息中的任何一个是由键盘生成的,则 wParam 将设置为目标图标的左上角。对于所有其他消息,wParam 未定义。
- GET_Y_LPARAM(wParam)返回为 X 锚定义的通知事件和消息的 Y 锚坐标。