Introduction
This article will present a reusable C++ trayicon class with a simple and easy to use interface. It is just a wrapper around the trayicon related WinAPI; it can be used in pure Win32 console/windowed applications, including the MFC based ones.
Background
In widget/component based systems like Delphi and .NET, it's easy to find a trayicon component that you just throw on your window, set up a few properties, and everything works fine. In C++, I wrote my own based on a component from one of my Delphi 4 projects. Coding a simple trayicon is not a big task, but there are some pitfalls, not to mention the unfriendly API interface that is not object oriented at all.
Pitfalls of coding a trayicon
Coding a trayicon in pure Win32 API basically means grouping together a unique trayicon ID, an icon, and a window handle (HWND
). You do everything by filling in a proper NOTIFYICONDATA
struct, and then calling the Shell_NotifyIcon()
function with the requested operation and your NOTIFYICONDATA
struct. The first unpleasant thing is that we need a HWND
, that usually makes it hard to write object oriented reusable code. This HWND
receives the events that happen to our small trayicon like mousemove, left-click, right-click etc... Another part of the game is filling out the NOTIFYICONDATA
struct correctly, that can cause headache for beginners. For example, with the old SDK for VC++ 6, this was the only valid way of filling in the cbSize member of our NOTIFYICONDATA
struct:
NOTIFYICONDATA data;
data.cbSize = sizeof(data);
With newer SDKs, the size of this structure is varying, and instead of writing sizeof(NOTIFYICONDATA)
, you have to use NOTIFYICONDATA_V1_SIZE
, or NOTIFYICONDATA_V2_SIZE
, or something else depending on the SDK/Windows version that is needed by the trayicon function you are about to use. This is a very common mistake. If you make an error here or somewhere else, then nothing happens and you waste your time searching for the problem in your code.
The basic functionality we usually expect from a trayicon
- Icon property.
- Name property. In my class, the
Name
property is the text of the tooltip of the trayicon. - Visible property. By changing this, you can show/hide your icon any time.
- Showing a balloon tooltip to notify the user about something important. (This feature works only on Win2K+, and detecting the user click (
NIN_BALLOONUSERCLICK
) on the balloon tooltip works only on WinXP+.) - We want to be able to capture mousemove and mouseclick events related to our trayicon with our event handler. For example, you might want to capture the left mouse click to bring your main window to the foreground, and tracking a popup menu on right-click is also a standard that users expect from you.
Using the code
You just create an instance of the class and set its properties. To capture the mouse events, you also have to set up a listener function or object for the icon, or you can derive your own trayicon class from CTrayIcon
by overriding the OnMessage()
method as an alternative to setting a listener.
OK, now let's discuss how this class provides the basic functionalities we listed in the previous section!
First, you have to create an instance of CTrayIcon
and set up its properties:
CTrayIcon tray_icon("example_icon", true, LoadIcon(NULL, IDI_APPLICATION));
The above code is equivalent to this:
CTrayIcon tray_icon;
tray_icon.SetName("example_icon");
tray_icon.SetIcon(LoadIcon(NULL, IDI_APPLICATION));
tray_icon.SetVisible(true);
You can change the properties anytime. For example, you can create an animating icon by periodically calling SetIcon()
with your icons, displaying the frames of a 2D animation.
In Win2K and later, you can call the ShowBalloonTooltip()
method to show a balloon tooltip to the user with a message. This is a good alternative to a MessageBox()
if your application is working in the background and you have to tell something to the user; for example:
tray_icon.ShowBalloonTooltip("GOOD NEWS", "Downloading xxx.dat has finished.", eTI_Info);
The balloon tooltip automatically disappears after some time (usually within 10 to 30 seconds). The amount of time it is being shown depends on a lot of factors, including your Operating System, Registry settings, and so on. If you want to create a tooltip that looks the same and provides more control over its behavior, then you have to search for a custom balloon tooltip control.
Now we already have a visible trayicon, can set its tooltip, icon, and visibility; the only problem is that we can not capture the mouse events. We have three options to solve this:
static void TrayIcon_OnMessage(CTrayIcon* pTrayIcon, UINT uMsg)
{
}
...
tray_icon.SetListener(TrayIcon_OnMessage);
class MyClass : public ITrayIconListener
{
public:
};
MyClass c;
tray_icon.SetListener(&c);
class CMyTrayIcon : public CTrayIcon
{
protected:
virtual void OnMessage(UINT uMsg)
{
}
};
CMyTrayIcon tray_icon;
For the sake of simplicity, I will use a static method as a listener:
static void TrayIcon_OnMessage(CTrayIcon* pTrayIcon, UINT uMsg)
{
switch (uMsg)
{
case WM_LBUTTONUP:
break;
case WM_RBUTTONUP:
break;
}
}
tray_icon.SetListener(TrayIcon_OnMessage);
Note that I listened to the WM_LBUTTONUP
message and not WM_LBUTTONDOWN
!!! This can be important! The first reason is that I wanted an OnClick()
event handler, and a click event is a pair of mousedown and mouseup events. Another scenario where using a mousedown event instead of a mouseup event is inappropriate is, for example, when you hide the icon in response to an event. In this case, you process the mousedown event, but after hiding your trayicon, the mouseup event is received by another trayicon, or maybe by the taskbar, and this usually results in bringing up a popupmenu for some other application without the user wanting this to happen. Some applications hide the trayicon when the main window is visible, and the above problem must be avoided if the user is able to restore the main window using a trayicon click/doubleclick.
The downloadable zip contains the trayicon sources, and a minimalist example program that shows you how to minimize your main window to tray. It also demonstrates the use of a simple popup menu on right-click.
History
- 15th Nov. 2010: Initial version.