Windows 7-style Notification Area Applications in WPF: Part 6 (Notify Icon Position: Pre-Windows 7)

View source on GitHub.

In Part 2 of this series I demonstrated how to use the Shell_NotifyIconGetRect function to find the position of a notify icon. This function is new to Windows 7, however, and we must find a different solution for earlier versions of Windows.

This turns out to be quite difficult. A post on the MSDN forums by user parisisinjail provided a good starting point, and it led me to a Code Project article by Irek Zielinski that explained exactly what to do – in native code. This post shows how to implement this kind of approach in managed code.

I have verified that the following code works with Windows XP and Vista. It also works under Windows 7, but only when the icon is not located in the notification area fly-out (not found in previous versions). As such, this solution should only be used when Shell_NotifyIconGetRect is not available.

The basic idea is that the notification area is actually just a special Toolbar control, with each icon being a toolbar button. We want to find the toolbar control and loop over the buttons until we find the one that corresponds to our notify icon. We can then find the coordinates of the toolbar button. (The fly-out in Windows 7 has a separate toolbar control, so you could search that instead of using Shell_NotifyIconGetRect if you really wanted to.)

The process sounds straight forward, but the implementation is quite tricky. Read on for the code.

Step 1: Find the Notification Area Toolbar

The first thing that we need to do is to find the handle of the notification area toolbar. If we open Spy++ (a tool included with Visual Studio), we can find a top-level window with the class name ‘Shell_TrayWnd’. This window represents the Windows taskbar (the notification area is a child window). While this class name is not documented anywhere as far as I can tell (meaning that it might change with a future version of Windows), it has been consistent since at least Windows XP. If we look at this window’s children, we’ll find some windows with the class name ‘ToolbarWindow32’. One of these points to the toolbar containing our notification icon.

Spy++ Window Showing Notification Area Toolbar

The screenshot above is of a system running Windows 7 x64. However, the arrangement of windows underneath the Shell_TrayWnd window varies from version-to-version: it has changed from Windows XP to Vista and again from Vista to 7. Furthermore, the names of windows vary according to the system’s language, so it’s not worth searching for a window called ‘User Promoted Notification Area’ when it might be called ‘사용자 프롬프트 알림 영역’ or any number of other strings (the names also change within language between Windows versions). In other words, we must be careful when we search for the toolbar’s handle.

With that in mind, let us continue.

Find Shell_TrayWnd

We’ll use the FindWindow function to search for window with the class name ‘Shell_TrayWnd’.

Enumerate Child Windows

Given the variable nature of the child windows, it seems to me that the best approach is to find all child windows with the class name ‘ToolbarWindow32’ and search through all of them until we find our notify icon. We’ll use the EnumChildWindows function to make a list of windows. The following code is from PInvoke.net, with a minor change to make the list include only ToolbarWindow32 windows.

We’ll call our GetChildWindows function using the pointer to the Shell_TrayWnd window we found earlier:

Step 2: Search Toolbars

We should now have a list with pointers to all the notification area toolbars. We’ll search through each toolbar until we find the notify icon (or run out of possibilities).

We next need to determine how many buttons are on the current toolbar. We can send a TB_BUTTONCOUNT message to the toolbar to find out.

Now we can loop through and find data about each button. This is harder than it sounds, since it turns out that we need to allocate memory within the toolbar’s process (and copy it back afterwards) – we can’t directly use memory belonging to our application. Irek Zielinski explains it more eloquently.

The GetWindowThreadProcessId function does what its name suggests:

Next, we’ll use the OpenProcess function to get a handle to the toolbar process:

Now we allocate memory within the toolbar’s process to store the information about each button:

We allocate enough memory to store a TBBUTTON struct, though we will use the same area to store a RECT struct later (if we find the icon). Since a RECT is smaller than a TBBUTTON, this is not a problem.

We now begin our loop through each button:

We’ll use the TB_GETBUTTON message to retrieve a TBBUTTON data structure for each button. We could alternatively use the TBBUTTONINFO structure, but it doesn’t provide us with any additional useful information in this case.

There should now be a populated TBBUTTON structure in the toolbar process’s memory: let’s retrieve it with ReadProcessMemory.

The TBBUTTON structure contains a member called ‘dwData’, which holds an ‘application-defined value’. In the case of the notification area, this happens to be a pointer to an IntPtr followed by a uint, holding the notify icon’s window handle and ID, respectively. Let’s retrieve those values. Note the overloads of ReadProcessMemory (two of several). Note also that it is important to use IntPtr, not int, since the length of the handle varies depending on whether the system is 32-bit or 64-bit.

Now all we need to do is check whether the current toolbar button is in fact our notify icon. (I demonstrated how to retrieve the handle and ID of a WinForms NotifyIcon in Part 2. I’ll assume those values are available here.)

If either condition is false, we’ll just continue on with the loop(s). If, on the other hand, we’ve found the notify icon, we need to find its coordinates: on to Step 3!

TBBUTTON Struct

You may notice that I’ve not defined the TBBUTTON struct yet. While the structure looks simple enough in the MSDN documentation, implementing it in managed code is a bit tough as its size varies depending on whether the operating system is 32-bit of 64-bit. Julian McFarlane blogged about this two years ago. I stole this image from his blog:

TBBUTTON Structure x86 vs x64

As you can see, the padding is 2 bytes long under 32-bit Windows, and 6 bytes long under 64-bit Windows. Since we don’t care about fsState and fsStyle for this project, the easiest solution is to simply use three IntPtrs for fsState/fsStyle/padding, dwData and iString. This will give us the correct alignment no matter the architecture. (It would of course be possible to retrieve fsState and fsStyle later, if needs be.)

Step 3: Determine Button’s Coordinates

Now that we’ve determined which toolbar button corresponds to our notify icon, we just need to find its coordinates. This is made simple by the TB_GETITEMRECT message and the MapWindowPoints function.

Step 4: Clean Up

When we deal with unmanaged code, it is important to free resources when we’re done with them, otherwise we’ll start leaking memory.

As promised earlier, look out for a sample solution with all the code from this series of blog posts put together. I’ll try and publish it soon.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *