Click here to Skip to main content
16,015,218 members
Articles / Mobile Apps / Windows Mobile
Article

Adding Icons to the System Tray

Rate me:
Please Sign up or sign in to vote.
4.90/5 (138 votes)
2 Aug 2003CPOL10 min read 2.9M   44.5K   503   553
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.

pWndWindow where notification messages will be sent. May be NULL
uCallbackMessageThe notification messages that will be sent to the parent window
szToolTipTooltip for the tray icon
iconHandle to the icon to be displayed
uIDTray icon ID
bHiddenIf 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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder
Canada Canada
Chris Maunder is the co-founder of CodeProject, DeveloperMedia and ContentLab, and has been a prominent figure in the software development community for nearly 30 years. Hailing from Australia, Chris has a background in Mathematics, Astrophysics, Environmental Engineering and Defence Research. His programming endeavours span everything from FORTRAN on Super Computers, C++/MFC on Windows, through to to high-load .NET web applications and Python AI applications on everything from macOS to a Raspberry Pi. Chris is a full-stack developer who is as comfortable with SQL as he is with CSS.

In the late 1990s, he and his business partner David Cunningham recognized the need for a platform that would facilitate knowledge-sharing among developers, leading to the establishment of CodeProject.com in 1999. Chris's expertise in programming and his passion for fostering a collaborative environment have played a pivotal role in the success of CodeProject.com. Over the years, the website has grown into a vibrant community where programmers worldwide can connect, exchange ideas, and find solutions to coding challenges. Chris is a prolific contributor to the developer community through his articles and tutorials, and his latest passion project, CodeProject.AI.

In addition to his work with CodeProject.com, Chris co-founded ContentLab and DeveloperMedia, two projects focussed on helping companies make their Software Projects a success. While at CodeProject, Chris' roles included Architecture and coding, Product Development, Content Creation, Community Growth, Client Satisfaction and Systems Automation, and many, many sales meetings. All while keeping his sense of humour.

Comments and Discussions

 
QuestionAnimation not working in VS2017/VS2019? Pin
Chengwei Lin at TW2-Aug-20 21:34
Chengwei Lin at TW2-Aug-20 21:34 
QuestionCommercial Use Pin
Adnan56214-Apr-20 23:48
Adnan56214-Apr-20 23:48 
QuestionUse In Commercial Application Pin
Member 1262264419-Jun-19 20:45
Member 1262264419-Jun-19 20:45 
QuestionUse in Commercial application Pin
Priyanka R Pawar10-Jan-18 17:43
Priyanka R Pawar10-Jan-18 17:43 
QuestionFailed icon recovery on WinXP Pin
ghotik200229-Jun-17 2:52
ghotik200229-Jun-17 2:52 
AnswerRe: Failed icon recovery on WinXP Pin
ghotik200230-Jun-17 3:43
ghotik200230-Jun-17 3:43 
QuestionWindows 10: ShowBalloon does not work if the user is not in Administrator group Pin
nomen15-Apr-16 11:09
nomen15-Apr-16 11:09 
AnswerRe: Windows 10: ShowBalloon does not work if the user is not in Administrator group Pin
nomen27-Apr-16 1:07
nomen27-Apr-16 1:07 
GeneralMy vote of 5 Pin
Mei You Qian20-Jan-16 1:22
professionalMei You Qian20-Jan-16 1:22 
QuestionUse in a commercial application Pin
Mei You Qian19-Jan-16 23:21
professionalMei You Qian19-Jan-16 23:21 
AnswerRe: Use in a commercial application Pin
Chris Maunder20-Jan-16 1:16
cofounderChris Maunder20-Jan-16 1:16 
GeneralDownloaded. Pin
NeverForever19-Jul-15 19:32
NeverForever19-Jul-15 19:32 
Questionhow showballon color change? Pin
Member 112817168-Feb-15 18:05
Member 112817168-Feb-15 18:05 
QuestionUse in commercial application Pin
Member 102580642-Feb-15 19:28
professionalMember 102580642-Feb-15 19:28 
AnswerRe: Use in commercial application Pin
Chris Maunder3-Feb-15 3:35
cofounderChris Maunder3-Feb-15 3:35 
GeneralMy vote of 5 Pin
Member 110740661-Oct-14 17:32
Member 110740661-Oct-14 17:32 
QuestionCan I use in our commercial application? Pin
CRSole18-Sep-14 8:05
CRSole18-Sep-14 8:05 
AnswerRe: Can I use in our commercial application? Pin
Chris Maunder18-Sep-14 8:18
cofounderChris Maunder18-Sep-14 8:18 
Questiongood resource Pin
saillimited14-Aug-14 19:26
saillimited14-Aug-14 19:26 
QuestionUse in a commercial application Pin
Member 1079076813-May-14 6:23
Member 1079076813-May-14 6:23 
AnswerRe: Use in a commercial application Pin
Chris Maunder14-May-14 6:26
cofounderChris Maunder14-May-14 6:26 
Questiondownlable Projects Pin
YOUSFI9-Jan-14 5:04
YOUSFI9-Jan-14 5:04 
QuestionShowIcon/HideIcon Pin
RJVB22-Jun-13 6:02
RJVB22-Jun-13 6:02 
AnswerRe: ShowIcon/HideIcon & EnableMenuItem Pin
RJVB26-Jun-13 1:56
RJVB26-Jun-13 1:56 
QuestionWhy do you hide the window like that? Pin
KEL36-Apr-13 2:18
KEL36-Apr-13 2:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.