You may have noticed that the notification area applications in Windows 7 (Volume/Power/Network/Action Centre) appear centred above their icon. I wanted Keiki to do the same; the current version is hardcoded to sit in the bottom right of the screen, which causes a few problems:
- The taskbar position is not taken into account; the window will be in the bottom right even if the taskbar is at the top of the screen.
- The window appears on top of the new Windows 7 fly-out interface for hiding notify icons if the Keiki icon is kept there.
In this post, I will demonstrate how to retrieve the location of a System.Windows.Forms.NotifyIcon with a function new to shell32.dll in Windows 7: Shell_NotifyIconGetRect. Windows Vista unfortunately lacks this function: I will cover the approach I use in Vista in a later post.
Thanks to Frédéric Hamidi for pointing me in the right direction.
The Shell_NotifyIconGetRect function takes two parameters: a NOTIFYICONIDENTIFIER (a structure that identifies the icon) and a RECT (a structure which will receive the coordinates of the icon) and returns an HRESULT indicating whether the method succeeds or not. So, how can we use this function in managed code?
PInvoke
When it comes to using unmanaged APIs within managed code, PInvoke.net is probably the best place to start. Many PInvoke signatures for the Win32 API can be found here. Regrettably, as Shell_NotifyIconGetRect is a new function, it doesn’t yet have an entry, so we have to work some stuff out for ourselves.
First, we write the PInvoke signature of the Shell_NotifyIconGetRect function. The syntax of the function is:
1 2 3 4 |
HRESULT Shell_NotifyIconGetRect( __in const NOTIFYICONIDENTIFIER *identifier, __out RECT *iconLocation ); |
Which gives us this signature:
1 2 |
[DllImport("Shell32", SetLastError = true)] private static extern Int32 Shell_NotifyIconGetRect([In] ref NOTIFYICONIDENTIFIER identifier, [Out] out RECT iconLocation<br />); |
We also need to define the two structures used in the function: NOTIFYICONIDENTIFIER and RECT (which is not compatible with System.Rect).
The NOTIFYICONIDENTIFIER structure looks like this:
1 2 3 4 5 6 |
typedef struct _NOTIFYICONIDENTIFIER { DWORD cbSize; HWND hWnd; UINT uID; GUID guidItem; } NOTIFYICONIDENTIFIER, *PNOTIFYICONIDENTIFIER; |
Converting that to C#, we get this:
1 2 3 4 5 6 7 |
private struct NOTIFYICONIDENTIFIER { public uint cbSize; public IntPtr hWnd; public uint uID; public Guid guidItem; } |
And the RECT structure looks like this:
1 2 3 4 5 6 |
typedef struct _RECT { LONG left; LONG top; LONG right; LONG bottom; } RECT, *PRECT; |
Which we can implement in C# as:
1 2 3 4 5 6 7 8 |
[StructLayout(LayoutKind.Sequential)] private struct RECT { public int left; public int top; public int right; public int bottom; } |
System.Windows.Forms.NotifyIcon
As there is currently no wrapper for Shell_NotifyIcon included with WPF, Keiki uses the System.Windows.Forms.NotifyIcon class to create its notification area icon. This hasn’t been an issue: I need only basic functionality, and there is the additional benefit (for me) of avoiding WPF menus, which imitate the style of native menus, but are in fact slightly different, thus sticking out (at least to my pedant eyes). The WinForms wrapper has its own downsides: it doesn’t support custom icons (introduced in XP SP2 but not important for my project) and it would be nice not to have to reference that namespace at all in a WPF program.
There are numerous high quality 3rd-party tray icon implementations for WPF: the best I’ve seen is WPF NotifyIcon by Philipp Sumi. However, I’ve decided to stick with the standard WinForms NotifyIcon for this project, mostly for simplicity’s sake. So, how can we use this class with the Shell_NotifyIconGetRect function that we just got working in managed code?
Notice that the documentation for NOTIFYICONIDENTIFIER states:
The icon can be identified to Shell_NotifyIconGetRect through this structure in two ways:
guidItem alone (recommended)
hWnd plus uIDIf guidItem is used, hWnd and uID are ignored.
Using a GUID to identify a notify icon is an approach that is new to Windows 7 and one that isn’t used by the WinForms NotifyIcon class. Thus, we need to find the notify icon’s hWnd (window handle) and uID so that we may pass them to our Shell_NotifyIconGetRect function.
Icon Handle & ID
Igor Kushnarev’s Code Project article ‘Embedding .NET Controls to NotifyIcon Balloon Tooltip’ was helpful for this section.
The list of properties for NotifyIcon isn’t very long, and it’s clear that hWnd and uID are not obviously accessible.
If we open up .NET Reflector and navigate to System.Windows.Forms.NotifyIcon, the disassembler shows us that there is indeed a variable called ‘id’:
1 |
private int id; |
We can use reflection in our code to retrieve this value at runtime:
1 2 |
FieldInfo idFieldInfo = notifyicon.GetType().GetField("id", BindingFlags.NonPublic | BindingFlags.Instance); int iconid = (int)idFieldInfo.GetValue(notifyicon); |
where ‘notifyicon’ is an instance of System.Windows.Forms.NotifyIcon. (Some error handling code wouldn’t go astray here.)
Now we need to find the notify icon’s window handle. In .NET Reflector we can see a variable called ‘window’:
1 |
private NotifyIconNativeWindow window; |
System.Windows.Forms.NotifyIcon.NotifyIconNativeWindow inherits from System.Windows.Forms.NativeWindow, which has a property called Handle: this is what we were searching for.
1 2 3 |
FieldInfo windowFieldInfo = notifyicon.GetType().GetField("window", BindingFlags.NonPublic | BindingFlags.Instance); System.Windows.Forms.NativeWindow nativeWindow = (System.Windows.Forms.NativeWindow)windowFieldInfo.GetValue(notifyicon); IntPtr iconhandle = nativeWindow.Handle; |
Finally…
After what seems like a lot of effort (though not a lot of code), we can at last call our Shell_NotifyIconGetRect function!
1 2 3 4 5 6 7 8 |
RECT rect = new RECT(); NOTIFYICONIDENTIFIER nid = new NOTIFYICONIDENTIFIER() { hWnd = iconhandle, uID = (uint)iconid }; nid.cbSize = (uint)Marshal.SizeOf(nid); int result = Shell_NotifyIconGetRect(ref nid, out rect); |
If everything has happened as it should, ‘rect’ should now hold the coordinates of the notify icon. (Again, some error handling would be nice.)
In the next post in this series I will look at how to get the position of the taskbar so that we place the window correctly using the result from Shell_NotifyIconGetRect.
Interesting note: if the notify icon is inside the fly-out notification area box, Shell_NotifyIconGetRect will only return its position within that box if the box is currently open. If the box is closed, the result seems to point to the location of the notification area expansion arrow.