Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

A simple and easy to use trayicon

4.39/5 (14 votes)
17 Nov 2010CPOL5 min read 201.5K   2.5K  
A reusable trayicon class for C++.

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:

C++
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:

C++
// the constructor sets the name property
// to "example_icon", the visible property to true,
// and the icon property to LoadIcon(NULL, IDI_APPLICATION).
CTrayIcon tray_icon("example_icon", true, LoadIcon(NULL, IDI_APPLICATION));

The above code is equivalent to this:

C++
CTrayIcon tray_icon;
tray_icon.SetName("example_icon");
tray_icon.SetIcon(LoadIcon(NULL, IDI_APPLICATION));
tray_icon.SetVisible(true); // the icon is invisible by default

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:

C++
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:

C++
// Option #1: sending events to a static method
static void TrayIcon_OnMessage(CTrayIcon* pTrayIcon, UINT uMsg)
{
  // your code here
}
...
tray_icon.SetListener(TrayIcon_OnMessage);

// Usage #2: sending events to our object
// implementing the ITrayIconListener interface
class MyClass : public ITrayIconListener
{
public:
  // override ITrayIconListener methods here
};
MyClass c;
tray_icon.SetListener(&c);

// Usage #3: deriving our own trayicon class and overriding OnMessage()
class CMyTrayIcon : public CTrayIcon
{
protected:
  virtual void OnMessage(UINT uMsg)
  {
    // your code here
  }
};
CMyTrayIcon tray_icon;

For the sake of simplicity, I will use a static method as a listener:

C++
static void TrayIcon_OnMessage(CTrayIcon* pTrayIcon, UINT uMsg)
{
  switch (uMsg)
  {
  case WM_LBUTTONUP:
    // here your bring your window to the foreground,
    // and maybe you hide your trayicon
    break;
  case WM_RBUTTONUP:
    // here your call GetCursorPos() and track a popupmenu at that position
    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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)