Search code examples
windowswinapisystem-traytrayiconshell32.dll

Difference between NOTIFYICON_VERSION and NOTIFYICON_VERSION_4 used in NOTIFYICONDATA structure?


When adding a system tray icon from in Windows there are two versions of API that we can pass to Shell_NotifyIcon() via NOTIFYICONDATA structure. There are subtle differences between the two API, and these are not listed anywhere on MSDN. It took me some effort to figure out some of the differences, which I am going to share now. Improvements/additions to the answer are always welcome.

PS: This question is purely for sharing what I have learnt over last few days experimenting with windows DPI scaling.


Solution

  • uVersion member of the NOTIFYICONDATA structure can have 3 possible values, representing the version of the API being used to create the taskbar icon.

    • 0 Use this value for applications designed for Windows versions prior to Windows 2000.
    • NOTIFYICON_VERSION Use the Windows 2000 behavior. Use this value for applications designed for Windows 2000 and later.
    • NOTIFYICON_VERSION_4 Use the current behavior. Use this value for applications designed for Windows Vista and later.

    When it comes to message handler for the tray icon, the wParam, and uParam have the differences as illustrated in the following image.

    NOTIFYICON_VERSION vs NOTIFYICON_VERSION_4

    Notice that in NOTIFYICON_VERSION_4 the wParam gives X, and Y coordinates of various events, but there is no provision for getting the coordinates in NOTIFYICON_VERSION. This gives rise to an interesting behaviour (which was a cause of a BUG I was trying to solve). If you use NOTIFYICON_VERSION, and then invoke the context menu of the tray icon, then the mouse cursor, wherever it may be while you are invoking the menu, gets placed right at the center of the tray icon. Even if you use keyboard (WINDOWS+B) for invoking context menu of the icon, the mouse cursor still moves to the icon.

    This may not be of particular interest to you until you look at this particular BUG I am trying to solve in Pico torrent application.

    Here is the scenario.

    • OS : Windows 10
    • Application isn't per-monitor DPI aware, but is system level DPI aware.
    • There is an initial value of Desktop scaling set, say 150%, when the user logs in.
    • Pico torrent is running.
    • DPI scaling value is changed to, say 125%
    • Pico torrent's context menu is invoked The context menu will not be displayed at its proper place, and will be displaced a little, showing a deviance.

    See the following images to understand what's happening.

    DPI scaling level : 150 % DPI scaling level : 125 %

    The problem is that although MSDN says that GET_X_LPARAM(wParam), and GET_Y_LPARAM(wParam) should give correct values in the handler of tray icon, but it doesn't, in the presence of DPI scaling (i.e. for a change in DPI scaling without doing a sign out and sign in). On the other hand the API GetCursorPos() returns the correct value of mouse cursor coordinates. Note that NOTIFYICON_VERSION_4 along with GetCursorPos() will not work, since the context menu can be invoked using keyboard, at which the mouse cursor can be anywhere on the screen(s).

    So, how do you combine all the knowledge just learnt to display the tray icon's context menu correctly when DPI scaling is done in the manner above, without making you application per-monitor DPI aware (for per-monitor DPI aware applications GET_X_LPARAM(wParam), and GET_Y_LPARAM(wParam) always return correct value)?

    Use NOTIFYICON_VERSION instead of NOTIFYICON_VERSION_4, this will position the mouse cursor at the tray icon when context menu is invoked, and then use GetCursorPos() to get mouse cursor's position. Display the context menu using TrackPopupMenu() with the coordinates.

    PS: In the example above the DPI scaling value is changed from 150% to 125%. The context menu deviance is more pronounced when DPI scaling is done from a bigger value to a smaller value, when your tray icon area is on lower right of the screen. This is because when DPI scaling is done, and windows magnifies UI elements which are not per-monitor aware, using DPI virtualization, then things move right-wards, and down-wards. eg. if in an application a windows rectangle is (0,0,100,100) (screen coordinates), then after magnification to 150%, it may become (0,0,150,150). Now for tray icon's menu, if you specify coordinates which lie beyond bottom-right of the screen, then the OS will still display is at a bottom right position which lies inside the screen, and which ensures that menu is displayed properly. eg. if a screen is 1920x1080, and TrackPopupMenu()is given (10000,10000) for menu, the menu will still be displayed inside the 1920x1080 screen rectangle. Thus increasing DPI scaling will not move the context menu any further, if it has already reached the right-bottom most position.