-1

在 Windows 中添加系统托盘图标时,我们可以Shell_NotifyIcon()通过NOTIFYICONDATA结构传递两个版本的 API。这两个 API 之间有细微的差别,这些都没有在 MSDN 上的任何地方列出。我花了一些精力来找出其中的一些差异,我现在将分享这些差异。总是欢迎对答案进行改进/补充。

PS:这个问题纯粹是为了分享我在过去几天尝试Windows DPI缩放所学到的东西。

4

3 回答 3

3

uVersionNOTIFYICONDATA结构的成员可以有 3 个可能的值,代表用于创建任务栏图标的 API 版本。

  • 0将此值用于为 Windows 2000 之前的 Windows 版本设计的应用程序。
  • NOTIFYICON_VERSION使用 Windows 2000 行为。将此值用于为 Windows 2000 和更高版本设计的应用程序。
  • NOTIFYICON_VERSION_4使用当前行为。将此值用于为 Windows Vista 和更高版本设计的应用程序。

对于托盘图标的消息处理程序wParam, 和uParam具有如下图所示的差异。

NOTIFYICON_VERSION 与 NOTIFYICON_VERSION_4

请注意,在NOTIFYICON_VERSION_4wParam 中给出了各种事件的 X 和 Y 坐标,但没有提供获取坐标的规定NOTIFYICON_VERSION。这引起了一个有趣的行为(这是我试图解决的一个 BUG 的原因)。如果您使用NOTIFYICON_VERSION, 然后调用托盘图标的上下文菜单,那么鼠标光标(无论您在调用菜单时可能在哪里)都会被放置在托盘图标的中心。即使您使用键盘(WINDOWS + B)调用图标的上下文菜单,鼠标光标仍会移动到图标上。

在您查看我试图在Pico torrent应用程序中解决的这个特定 BUG 之前,您可能不会特别感兴趣。

这是场景。

  • 操作系统:Windows 10
  • 应用程序不是每个显示器的 DPI 感知的,而是系统级的 DPI 感知的。
  • 当用户登录时,有一个桌面缩放集的初始值,比如 150%。
  • Pico torrent 正在运行。
  • DPI 缩放值更改为,例如 125%
  • Pico torrent的上下文菜单被调用 上下文菜单不会显示在正确的位置,并且会稍微移位,显示出偏差。

请参阅以下图像以了解正在发生的事情。

DPI 缩放级别:150 % DPI 缩放级别:125 %

问题是,虽然MSDN这么说GET_X_LPARAM(wParam),并且GET_Y_LPARAM(wParam)应该在托盘图标的处理程序中给出正确的值,但是在存在 DPI 缩放的情况下(即,在没有注销和登录的情况下更改 DPI 缩放) . 另一方面,APIGetCursorPos()返回鼠标光标坐标的正确值。请注意,NOTIFYICON_VERSION_4withGetCursorPos()不起作用,因为可以使用键盘调用上下文菜单,鼠标光标可以位于屏幕上的任何位置。

那么,当以上述方式完成 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 缩放将不会进一步移动上下文菜单。

于 2017-01-14T12:05:01.097 回答
1

@sahil-singh 你是对的,我同意其他人的观点,即你的应用程序应该是 DPI 感知的,但是这不是重点。

我有一个类似的问题,我的应用程序(仍然)不支持 DPI 并且 GET_X_LPARAM(wParam) 将返回非虚拟坐标。将此值传递给 TrackPopupMenu() 后,我在屏幕上的位置错误。

最好的方法是使用 GetMessagePos() 而不是 wParam。在这种情况下,Windows 将为您提供带有虚拟坐标的新 DWORD,然后使用 GET_X_LPARAM/GET_Y_LPARAM 获取可以传递给 TrackPopupMenu() 的值。

于 2017-02-21T11:40:53.567 回答
0

这似乎(2 年后的 2019 年)记录在 MSDN 上:

NOTIFYICONDATAA 结构体

uCallbackMessage

类型:UINT

uVersion成员为 0 或 NOTIFYICON_VERSION 时,消息的wParam参数包含发生事件的任务栏图标的标识符。该标识符的长度可以是 32 位。lParam参数保存与事件相关的鼠标或键盘消息。例如,当指针移到任务栏图标上时,lParam设置为WM_MOUSEMOVE

uVersion成员为 NOTIFYICON_VERSION_4 时,应用程序继续通过uCallbackMessage成员以应用程序定义的消息的形式接收通知事件,但对该消息的lParamwParam参数的解释发生了如下变化:

  • 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 锚坐标。
于 2019-02-11T21:58:18.820 回答