Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Adding Icons to the System Tray

0.00/5 (No votes)
2 Aug 2003 285  
A class for adding icons to the system tray
Win9x sample winCE sample win2000 sample
Example images in Windows 9x, Windows CE and Windows 2000

Introduction
Construction
Operations
Icon
Minimising an application to the system tray
Default message handling
Example of use
NOTE on TrackPopupMenu
History
Latest

Introduction

CSystemTray is a conglomeration of ideas from the MSJ "Webster" application, sniffing round the online docs, from other implementations such as PJ Naughter's "CTrayNotifyIcon" ( http://indigo.ie/~pjn/ntray.html), and from the many contributions from other developers.

This class is a light wrapper around the windows system tray stuff. It adds an icon to the system tray with the specified ToolTip text and callback notification value, which is sent back to the Parent window.

The Old way:

The basic steps to using a tray icon via the windows API are:

  1. Load up the NOTIFYICONDATA structure
  2. Call Shell_NotifyIcon(NIM_ADD, &MyTrayNotifyStruct)

Changing the values of the fields in NOTIFYICONDATA and calling Shell_NotifyIcon allows you to change to icon or tool tip text or remove the icon itelf. All this messing around has been bundled in a class wrapper to make it easier and neater.

The Better way

The simpler way to add an icon to the system tray is to create an object of type CSystemTray either as a member variable or dynamically. Two forms of the constructor allow the programmer to insert the icon into the tray as the CSystemTray object is created, or by using the member function CSystemTray::Create. eg.

CSystemTray m_TrayIcon;   // Member variable of some class

... 
// in some member function maybe...

m_TrayIcon.Create(pParentWnd, WM_MY_NOTIFY, "Click here", 
                  hIcon, nTrayIconID);

This will insert an icon in the system tray. See the following section for details.

To MFC or not to MFC...

There are two forms of the class: MFC and Non-MFC. They are in the SystemTray.* and SystemTraySDK.* files respectively. The MFC version has been written to use MFC classes (CWnd etc) whereas the non-MFC will use HWND's. I may in future revise this so they both use HWND's for optimum compatibility.

Both classes have essentially the same functionality, excepting that the non-MFC version only supports a single tray icon per application. Because the non-MFC version is not derived from CWnd you need to be careful about the window you choose to receive the icon messages. If you set the parent of the icon as NULL, then the tray icon will handle it's own tray notification messages - but will also try and handle the menu commands sent from the context menu for the icon. To get around this you need to either:

  1. Set the parent of the tray icon as a window that will handle all tray icon notifications, or
  2. Set the parent as NULL, and use CSystemTray::SetTargetWnd to nominate the window that will receive the menu commands.

Eg. For a non-MFC tray icon, do the following:

CSystemTray m_TrayIcon;   // Member variable of some class

... 
// in some member function maybe...

m_TrayIcon.Create(hInstance, NULL, WM_MY_NOTIFY, 
                  "Click here", hIcon, nID);

// Send all menu messages to hMyMainWindow

m_TrayIcon.SetTargetWnd(hMyMainWindow);    

Construction

CSystemTray();
CSystemTray(CWnd* pWnd, UINT uCallbackMessage, LPCTSTR szToolTip,
            HICON icon, UINT uID, BOOL bHidden = FALSE,
            LPCTSTR szBalloonTip = NULL, LPCTSTR szBalloonTitle = NULL, 
            DWORD dwBalloonIcon = NIIF_NONE, UINT uBalloonTimeout = 10);
BOOL Create(CWnd* pWnd, UINT uCallbackMessage, LPCTSTR szToolTip, 
            HICON icon, UINT uID, BOOL bHidden = FALSE,
            LPCTSTR szBalloonTip = NULL, LPCTSTR szBalloonTitle = NULL, 
            DWORD dwBalloonIcon = NIIF_NONE, UINT uBalloonTimeout = 10);

The non-MFC version includes an additional parameter (parameter 1) that represents the applications instance handle.

Note that the destructor automatically removes the icon from the tray.

pWnd Window where notification messages will be sent. May be NULL
uCallbackMessage The notification messages that will be sent to the parent window
szToolTip Tooltip for the tray icon
icon Handle to the icon to be displayed
uID Tray icon ID
bHidden If TRUE, the icon is initially hidden
szBalloonTip The balloon tip text (Windows 2000 only)
szBalloonTitle The balloon tip title (Windows 2000 only)
dwBalloonIcon The balloon tip icon (Windows 2000 only)
uBalloonTimeout The balloon tip timeout (Windows 2000 only)

If the pWnd parameter is NULL then the function CSystemTray::OnTrayNotification will be called whenever the icon sends a notification message. See Default message handling for more details.

Operations

LRESULT OnTrayNotification(WPARAM wID, LPARAM lEvent) // Discussed below


void MoveToRight()           // Moves the icon to the far right of the tray, 

                             // so it is immediately to the left of  the clock 

void HideIcon()              // Hides but does not totally remove the icon

                             // from the tray. 

void RemoveIcon()            // Removes the icon from the tray (icon can no 

                             // longer be manipulated)

void ShowIcon()              // Redisplays a previously hidden icon

void AddIcon()               // Adds the icon to the tray   

void SetFocus(               // Sets the focus to the icon (Win2000 only)


BOOL ShowBalloon(LPCTSTR szText,     // Shows the balloon tip (Win2000 only)

                 LPCTSTR szTitle = NULL,   
                 DWORD dwIcon = NIIF_NONE, 
                 UINT uTimeout = 10);

BOOL    SetTooltipText(LPCTSTR pszTip) // Set Tooltip text

BOOL    SetTooltipText(UINT nID)       // Set tooltip from text resource ID

CString GetTooltipText() const         // Retrieve tool tip text

BOOL    SetNotificationWnd(CWnd* pWnd) // Self explanatory

CWnd*   GetNotificationWnd() const
BOOL    SetTargetWnd(CWnd* pTargetWnd);// Change or retrieve the window to

CWnd*   GetTargetWnd() const;          //  send menu commands to


BOOL    SetCallbackMessage(UINT uCallbackMessage) // Self explanatory

UINT    GetCallbackMessage() const;

HICON   GetIcon() const                // Get current tray icon

BOOL    SetIcon(HICON hIcon)           // Change tray icon. Returns FALSE 

                                       //  if unsuccessful

BOOL    SetIcon(LPCTSTR lpszIconName)  // Same, using name of the icon resource

BOOL    SetIcon(UINT nIDResource)      // Same, using icon resource ID


BOOL    SetStandardIcon(LPCTSTR lpIconName)  // Load icon from the

BOOL    SetStandardIcon(UINT nIDResource)    //   current application

    
// Set list of icons for animation 

BOOL    SetIconList(UINT uFirstIconID, UINT uLastIconID);
BOOL    SetIconList(HICON* pHIconList, UINT nNumIcons); 

// Start animation

BOOL    Animate(UINT nDelayMilliSeconds, int nNumSeconds = -1);   
BOOL    StepAnimation();                       // Step to next icon

BOOL    StopAnimation();                       // Stop animation


BOOL SetMenuDefaultItem(UINT uItem, BOOL bByPos);   // Set default menu item

void GetMenuDefaultItem(UINT& uItem, BOOL& bByPos); // Get default menu item

    
static void MinimiseToTray(CWnd* pWnd);
static void MaximiseFromTray(CWnd* pWnd, CRect rectTo);
static void MaximiseFromTray(CWnd* pWnd, LPCREATESTRUCT lpCreateStruct);

 
virtual void CustomizeMenu(CMenu*) // Customise the context menu before it's

                                   // displayed

SetStandardIcon can also take any of the following values (not available in WinCE):

nIDResource Description
IDI_APPLICATION Default application icon.
IDI_ASTERISK Asterisk (used in informative messages).
IDI_EXCLAMATION Exclamation point (used in warning messages).
IDI_HAND Hand-shaped icon (used in serious warning messages).
IDI_QUESTION Question mark (used in prompting messages).
IDI_WINLOGO Windows logo

The default CSystemTray message notification handler searches and displays the menu with the same ID as the tray icon. If you use this default handler then you can set the default menu item using SetMenuDefaultItem. The parameters uItem and bByPos are the same as those used in ::SetMenuDefaultItem.

The default menu item code was contributed by Enrico Lelina.

Minimising an application to the system tray

Two functions have been provided to allow you to easily "minimise" an application to the system tray:

    static void MinimiseToTray(CWnd* pWnd);
    static void MaximiseFromTray(CWnd* pWnd);

where pWnd is the window to minimise or maximise (usually your main application window).

"Minimising to the system tray" means that the applications main window is minimised using the DrawAnimatedRects function to make it appear that it is collapsing into the system tray. The main applications window is then made invisible and the applications icon is removed from the task bar. To minimise an application to the system tray simply call MinimiseToTray. System tray minimisation was inspired by Santosh Rao.

If an application has been minimised to the tray, then it can be maximised again by calling MaximiseFromTray. For example, if you have a CDialog derived class that you display in response to a user double-clicking on the tray icon (or selecting a menu item from the popup menu), then override the OnDestroy and OnShowWindow functions in your CDialog class and add the following lines:

void CMyDialog::OnDestroy() 
{
    CDialog::OnDestroy();
    CSystemTray::MinimiseToTray(this);
}

void CMyDialog::OnShowWindow(BOOL bShow, UINT nStatus) 
{
    CDialog::OnShowWindow(bShow, nStatus);
    
    if (bShow)
         CSystemTray::MaximiseFromTray(this);
}

Icon animation

Icon animation can be achieved by specifying a list of icons using SetIconList(...), with either a range of icon resource IDs, or an array of HICONs and the size of the array. and calling Animate(UINT nDelayMilliSeconds, int nNumSeconds). The first parameter is the delay in milliseconds between each animation frame, and the second is the numer of seconds for which to animate the icon. If -1 is specified then animation will continue until StopAnimation() is called. Icon animation was suggested by Joerg Koenig.

Default message handling

The parent window, on receiving a notification message, can redirect this message back to the tray icon for handling by calling CSystemTray::OnTrayNotification(...). Alternatively, if the CSystemTray object was created with a NULL parent, then this function will be called whenver the icon sends a notification. The default implementation tries to find a menu with the same resource ID as the tray icon. If it finds a menu and the event received was a right mouse button up, then the submenu is displayed as a context menu. If a double click was received, then the message ID of first item in the submenu is sent back to the parent window.

Using the new Windows 2000 / IE5 Balloon tips

The new balloon tips rely on Shell32.dll to be version 5.0 or later, and for the the _WIN32_IE to be defined as 0x0500 or higher in your source code. There are two approaches you can use:

  • Assume that your users will have version 5.0 installed and add #define _WIN32_IE 0x0500

  • Do it properly and use DllGetVersion to get the version of Shell32.dll and adjust your code accordingly. Thanks to Porgee for quoting the MSDN tip:

    Use the DllGetVersion function to determine which Shell32.dll version is installed on the system. If it is version 5.0 or greater, initialize the cbSize member with:

    nid.cbSize = sizeof(NOTIFYICONDATA);

    Setting cbSize to this value enables all the version 5.0 and 6.0 enhancements. For earlier versions, the size of the pre-6.0
    structure is given by the NOTIFYICONDATA_V2_SIZE constant and the pre-5.0 structure is given by the NOTIFYICONDATA_V1_SIZE constant. Initialize the cbSize member with:

    nid.cbSize = NOTIFYICONDATA_V2_SIZE;

    Using this value for cbSize will allow your application to use NOTIFYICONDATA with earlier Shell32.dll versions, although without the version 6.0 enhancements.

I currently use the lazy method. There is a define ASSUME_IE5_OR_ABOVE in the system tray header that determines whether or not version 5.0 or above should be assumed present. Comment this out for applications targeting Shell32.dll versions less than 5.0.

Example of use

A good place to declare the tray icon is in your CFrameWnd derived class.
eg.

#define WM_ICON_NOTIFY  WM_APP+10

CSystemTray m_TrayIcon

Add a message map entry for the tray icon notification:

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    ...
    ON_MESSAGE(WM_ICON_NOTIFY, OnTrayNotification)
END_MESSAGE_MAP()

Create the icon (maybe in your OnCreate):

if (!m_TrayIcon.Create(this, WM_ICON_NOTIFY, strToolTip, 
                       hIcon, IDR_POPUP_MENU))
    return -1;

where IDR_POPUP_MENU is the ID of a popup menu to display for the icon. You then need a handler for the tray icon notification message:

LRESULT CMainFrame::OnTrayNotification(WPARAM wParam, LPARAM lParam)
{
    // Delegate all the work back to the default 

        // implementation in CSystemTray.

    return m_TrayIcon.OnTrayNotification(wParam, lParam);
}

The menu used (IDR_POPUP_MENU) looks like the following:

IDR_POPUP_MENU MENU PRELOAD DISCARDABLE 
BEGIN
    POPUP "POPUP_MENU"
    BEGIN
        MENUITEM "&About...",      ID_APP_ABOUT
        MENUITEM SEPARATOR
        MENUITEM "&Options...",    ID_APP_OPTIONS
        MENUITEM SEPARATOR
        MENUITEM "E&xit",          ID_APP_EXIT
    END
END

A single right click on the tray icon will bring up this menu, while a double click will send a ID_APP_ABOUT message back to the frame. (Note that in CE, ALT+Left button will bring up the menu, and ALT+Double Click will perform the default action).

Alternatively, you can m_TrayIcon.Create(NULL, ...) and leave out the message map entry for WM_ICON_NOTIFY. The default implementation of CSystemTray will take care of the rest.

NOTE on TrackPopupMenu

Many people have had troubles using TrackPopupMenu. They have reported that the popup menu will often not disappear once the mouse is clicked outside of the menu, even though they have set the last parameter of TrackPopupMenu() as NULL. This is a Microsoft "feature", and is by design. The mind boggles, doesn't it?

Anyway - to workaround this "feature", one must set the current window as the foreground window before calling TrackPopupMenu. This then causes a second problem - namely that the next time the menu is displayed it displays then immediately disappears. To fix this problem, you must make the currernt application active after the menu disappears. This can be done by sending a benign message such as WM_NULL to the current window.

So - what should have been a simple:

TrackPopupMenu(hSubMenu, TPM_RIGHTBUTTON, pt.x,pt.y, 0, hDlg, NULL);

becomes

SetForegroundWindow(hDlg);
TrackPopupMenu(hSubMenu, TPM_RIGHTBUTTON, pt.x,pt.y, 0, hDlg, NULL);
PostMessage(hDlg, WM_NULL, 0, 0);

Refer to KB article "PRB: Menus for Notification Icons Don't Work Correctly" for more info.

History

I updated the class so it would work in CE. A CE demo project is now included.

Thomas Mooney helped make changes that allows the class to be used in an NT service. The problem occured when using CSystemTray in an application that runs as a service on NT. When NT starts, the application starts. Trouble is, there is no taskbar, no system tray, nowhere to put the icon until someone has logged on. This has been fixed.

Michael Dun added Windows 2000 support - namely those way cool balloon tips.

The class now supports the new Windows2000 features, as well as CE (including popup menus from the tray). Two new functions for minimising an application to the system tray have also been added.

21 Sep 2000 - Matthew Ellis has improved the minimise-to-tray functionality by providing an improved version of the GetTrayWndRect function that searches for the location of the system tray. He has also provided a function GetDoWndAnimation that checks if the system has been set to show window animation (for minimise/maximise), and if not, no animation will be shown.

There is also a non-MFC version.

H�kan Trygg has updated the class with the following:

Instead of always sending the menu messages to the Main window (AfxMainWnd) they get sent to another, specified window. It's called the target window. If there is no specified window then AfxMainWnd is used.

The new functions are

    BOOL  SetTargetWnd(CWnd* pTargetWnd);
    CWnd* GetTargetWnd() const;

Also: the creation flags of the tray icon are saved so that if the icon needs to be recreated (settings change, taskbar recreated etc) then the icon will be created properly.

16 Jun 2002 Added the ASSUME_IE5_OR_ABOVE define and fixed VC 7.0 compile errors.

3 Aug 2003 - added a bunch of small fixes as well as the CustomizeMenu method. Thanks to Anton Treskunov for this and the fixes.


H�kan Trygg has also methods added to hold and change the menu.
    BOOL SetMenuText(UINT uiCmd, LPCTSTR szText);
    BOOL SetMenuText(UINT uiCmd, UINT uiID);
    BOOL CheckMenuItem(UINT uiCmd, BOOL bCheck);
    BOOL EnableMenuItem(UINT uiCmd, BOOL bEnable);
    BOOL DeleteMenu(UINT uiCmd);

The updated class files can be downloaded here. They have not been merged into the main class yet simply becuase I haven't had time to test - but I felt them important enough that I didn't want to delay making them available.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here