Adding Icons to the System Tray

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

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

Introduction
Construction
Operations
Default message handling
Example of use
NOTE on TrackPopupMenu

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

Introduction

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” (
http://indigo.ie/~pjn/ntray.html
) 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.

Construction

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


















  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

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    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.
eg.

	#define WM_ICON_NOTIFY  WM_USER+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.

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.

Last updated: 25 April 1998

More by Author

Previous article
Next article

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read