Broccoli Products home | contact us | about us

TrayIcon for a WPF Application without using WinForms
(by Lou Brown, last updated 17-FEB-2011)

The Brief
This is for anyone who wants to minimize their WPF application down to the system tray.

More specifically I want to create a WPF application that when minimized, the application icon on the taskbar disappears and an icon in the system-tray appears. When the user double-clicks the system-tray icon, the WPF window is restored to normal size, the tray icon disappears, and the application icon on the task bar reappears.

I found some examples of how to do this on the web, but they rely on the WinForms libraries which include the Forms.NotifyIcon class. This nagged me a bit.  When writing a WPF application, I like to keep it WPF, and not have to import an unrelated presentation library just to pull over a single feature. The solution is to use the system-level features of the Windows system within the WPF application.

WPF and System-Level Programming
WPF and system-level programming don’t really go together, so some rummaging in the Windows SDK is required. Hopefully my blurb will explain away all the technicalities, but there are a few gotchas.

There is some sample code you can download - two class files and a sample project. The code has been developed and tested on Windows 7, C# .Net 4, and VS 2010. If you are using Vista, you need to ask yourself why.

File: TrayIconLab.zip
Current Build: 15th FEB 2011
System Requirements:    Windows 7, VS 2010, .Net 4
 
File: TrayIcon.cs
Current Build:    15th FEB 2011
 
File: HookWrapper.cs
Current Build:    15th FEB 2011

What is a System-Tray Icon?
Firstly, so that we’re all on the same page, system-tray icons are the icons situated on the right side of the task-bar, at the bottom of the screen (assuming you haven’t been moving things around), next to the clock. System-tray icons differ from the usual program icons (task-bar icons), which appear on the main body of the task-bar.

Developers choose to use system-tray icons for applications that are always running in the background. For example, BitTorrent has a system-tray icon.

In Windows 7 some system-tray icons are not always visible, because they are relegated to the “hidden system-tray”. This is to prevent your system-tray from filling up with icons that never change, for example, a sound card application sometimes has a pointless system-tray icon. So…

GOTCHA 1 You create a system-tray icon, the function returns ok, but where is it? Check the hidden system-tray. One feature I noticed during testing, if you add a system-tray icon, then remove it, then add it again, it appears in the hidden system-tray. You can open the hidden system-tray and stop this behavior by clicking the customize option, and making sure the VHost32 application icons are set to always be visible.

Background
To get started, two things must you know before creating a system tray icon class – how to hook Windows messages, and the Shell_NotifyIcon function.

Hooking Windows messages is a way of snooping on the messages going to a window. By “Windows messages” I mean WM_PAINT, WM_MOUSEMOVE etc.

The Shell_NotifyIcon function is a system-level function that adds, modifies or deletes an icon in the system tray. Instead of showing and hiding system-tray icons, we will be adding and removing them.

Hooking Messages – HookWrapper.cs
As mentioned above, “hooking” is a way of snooping on the Windows messages going to a window. Hooks work by placing callback functions in the message loop. Within these functions the messages can be glimpsed before or after they have been processed by the window.

Hooks are daisy chained together, one hook being called after another. So in your hook functions you must always finish by calling the hook function again to maintain the chain.

Hooks have many uses, such as low-level message handling. In the old days we used hooks to make redrawing more efficient by chopping out unnecessary WM_PAINT messages.

Hooks also allow windowless classes to process the messages for a target window without the developer having to drastically change the code for that target window. So our HookWrapper can hook windows messages, examine them, react to them, but we do not have to insert a chunk of code into the window. This makes our HookWrapper class nicely pluggable – a single code file and to implement, simply call the constructor from within the window class.

If you open the HookWrapper.cs file, we can go through it.

The HookWrapper class is a wrapper for the Window’s system hooking functions.

At the top of the class is the import section where all the bits of the Window system that we are going to use are declared. Hooking requires three system-level functions - one to set up the hook, one to let go of the hook, and the callback function to pass the hook call down the chain.

The HookWrapper class implements the IDisposable interface. Since we are using unmanaged resources, it is a good idea to make sure we clean up after ourselves. (I notice developers who put code on the web don't use the IDisposable interface and I think this is why the whole planets going down the toilet.)

Note the pragma in the constructor, cutting out the compiler warning about AppDomain.GetCurrentThreadId. It is a deprecated function. The Windows system does not guarantee the thread id will not change during its lifetime. In GetCurrentThreadId ‘s defense can I point out that, “A” the suggested replacement function doesn't’t work with hooking, and “B”, in Microsoft’s example code for hooking in C#, they use the deprecated function as well. And since the thread is the applications UI thread, the chances of it jumping horses in mid-stream are low. But not zero. Just low.

Once a window is hooked, the _localCallbackFunction Is called by the system for each message. In this function we can examine the messages, and pass them over to the owner of the HookWrapper object.

So you may have guessed by now, seeing as hook callback functions are called directly from the message loop, it is not a good idea to put large chunks of time-consuming code in these functions, or the efficiency of your entire Windows session will deteriorate.

For a window class to use the HookWrapper class, the window must call the constructor. The constructor requires a reference to window, a function that will handle incoming messages, and a message filter value. To stop hooking, call HookWrapper.Dispose.

GOTCHA 2 In the HookWrapper constructor, instead of passing our callback function, _localCallbackFunction, to the SetWindowsHookEx function directly, a new instance of a delegate is created using _localCallbackFunction and a pointer to this delegate is used. If instead, the function _localCallbackFunction is passed directly into the SetWindowsHookEx, no managed object would be referencing it, and the garbage collector would have eaten it. Then the hook will be calling back on a garbage collected function, and it will be like The End of Days for your application. So don’t be tempted to reduce those two lines.

Well, that’s HookWrapper.cs, which is the more technically difficult file. Now open the TrayIcon.cs file.

The Tray Icon Class - TrayIcon.cs
Again, a load of hideous importing at the top. I put regions around import sections to hide it. Our import section is made simple because the Windows’s system-tray icon feature hangs on one function, Shell_NotifyIcon.

Below the imports an event is defined, LeftDoubleClick. This event is fired when the user double-clicks the tray icon.

In the Attributes (or members) section, there is our HookWrapper, m_hook. The hook object is constructed when the system-tray icon is shown, and disposed of when it is hidden.

NOTIFYICONDATA is a data structure that goes with the Shell_NotifyIcon function, and to save having to populate it from the start each time it is needed, I keep hold of one as a member variable.

Below the Attributes is the Constructor section. Note that the only construction parameter required is a reference to the main window. From this reference we get a handle. Also in the constructor, we set the guts of the NOTIFYICONDATA structure.

GOTCHA 3 If you attempt to get the handle of the main window of a WPF application in the constructor of the main window, the handle will be null. Window handles in WPF are not same as in the old days. Wait until the SourceInitialized event is fired before calling the TrayIcon constructor.

Again, the IDisposable interface is implemented. So many apps these days fall over and leave an icon in the system-tray, which then disappears when the mouse is waved over it. Any luck, our dispose function will mop ours up for us. (However there is a Windows shell function that is available for creating system-tray icons that hang around when the owner process is dead. But why would anyone want to do that?)

WM_TRAYICON is our windows message, which will be fired to our main window when someone double clicks our tray icon. This is why we need the hook – to pick up this message.

The functions for showing and hiding are called Show and Hide, but as previously mentions, it is not the visibility of the system-tray icon we are modifying. Instead we are adding and removing it from the tray.

The function SetTip modifies the tool-tip for the tray icon by making a modification call. You can change the code to change the icon as well if you want a flashing icon.

Finally, our _fnHookCallback function, called by the hook. This function receives every tray icon message sent. This include all mouse events, mostly of no interest. So we sift for the double-click message and if found, fire an event to the owner, which is the main window.

That’s the TrayIcon class done.

Implementation
So the changes to our WPF main window are small.

In the Attributes (or members) section, we declare a TrayIcon.

In the SourceInitialized event we construct the TrayIcon, passing a reference to the main window. We also register for double-click events.

In the window state-change event, we wait for the window to be minimized. If minimized, show the tray icon and hide the taskbar icon. If restoring or maximizing, hide the tray icon and show the taskbar icon.

In the event handler that is fired when the system-tray icon is double clicked, restore the window from minimized.

Voila!

Here is the source file again.

File: TrayIconLab.zip
Current Build: 15th FEB 2011
System Requirements:    Windows 7, VS 2010, .Net 4
 
File: TrayIcon.cs
Current Build:    15th FEB 2011
 
File: HookWrapper.cs
Current Build:    15th FEB 2011

Development note: The HookWrapper and TrayIcon classes are not specific to the application in the sample, so I've placed them in the BroccoliProducts namespace. You can change this to whatever you like, but suggest you get the sample working before changing any of the code.


MORE INFORMATION

MSDN - Hook Overview
MSDN - SetWindowsHookEx

MSDN - Shell_NotifyIcon


Contact form 
Use the contact form to send comments and requests for information to Broccoli Products.
Topic:
Message:
Email:
Broccoli Products Ltd © 1998-2012 Broccoli Products Ltd
Reg Number: 2895355
Reg Office: 27 Old Gloucester Street, London. WC1N 3AX
Privacy Policy
Copyright Notice
Liability Disclaimer
Contact Us