Adding Icons to the System Tray

SystemTray image  Download source files (4Kb) or Sample Project (37Kb).

Default message handling
Example of use
NOTE on TrackPopupMenu

Latest additions:
Icon animation added, default menu item selectable.


This is a conglomeration of ideas from the MSJ "Webster" application, sniffing round the online docs, and from other implementations such as PJ Naughter's "CTrayNotifyIcon" ( especially the "CSystemTray::OnTrayNotification" member function and the SetStandardIcon stuff.

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.


	CSystemTray(CWnd* pWnd, UINT uCallbackMessage, LPCTSTR szToolTip, HICON icon, UINT uID);
	BOOL Create(CWnd* pWnd, UINT uCallbackMessage, LPCTSTR szToolTip, HICON icon, UINT uID);

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

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.


	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    RemoveIcon()                            // Removes the icon from the tray (icon can no
	                                                //   longer be manipulated)
	void    HideIcon()                              // Hides but does not totally remove the icon
	                                                //   from the tray.
	void    ShowIcon()                              // Redisplays a previously hidden icon

	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

	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 current application.
	BOOL    SetStandardIcon(UINT nIDResource)       
	BOOL    SetIconList(UINT uFirstIconID, UINT uLastIconID);         // Set list of icons for animation 
	BOOL    SetIconList(HICON* pHIconList, UINT nNumIcons); 
	BOOL    Animate(UINT nDelayMilliSeconds, int nNumSeconds = -1);   // Start animation
	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

SetStandardIcon can also take any of the following values:

	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.

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. Use "Animate(UINT nDelayMilliSeconds, int nNumSeconds)" to start the animation. The first parameter is the delay in milliseconds between displaying each icon in the list, and the second is the number of seconds for which to animate the icons. If -1 is specified then animation will continue until StopAnimation() is called.

Icon animation 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 whenever 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 default item in the submenu (set using SetMenuDefaultItem) is sent back to the parent window. If SetMenuDefaultItem has not been called then the default menu item will be the first item in the menu

Example of use

A good place to declare the tray icon is in your CFrameWnd derived class.
	CSystemTray m_TrayIcon
Add a message map entry for the tray icon notification:
		ON_MESSAGE(WM_ICON_NOTIFY, OnTrayNotification)
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:
			MENUITEM "&About...",      ID_APP_ABOUT
			MENUITEM "&Options...",    ID_APP_OPTIONS
			MENUITEM "E&xit",          ID_APP_EXIT
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.

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);
	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.

Last updated: 25 April 1998


  • Re:

    Posted by icon design on 09/14/2012 09:22am

    Yes, really. I join told all above. We can communicate on this theme. P.S. Please review our icons for Windows and windows12icons.

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Live Event Date: November 13, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT APIs can be a great source of competitive advantage. The practice of exposing backend services as APIs has become pervasive, however their use varies widely across companies and industries. Some companies leverage APIs to create internal, operational and development efficiencies, while others use them to drive ancillary revenue channels. Many companies successfully support both public and private programs from the same API by varying levels …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds