分类: C/C++
2008-08-25 18:03:05
Example images in Windows 9x, Windows CE and Windows 2000 |
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" (), 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:
NOTIFYICONDATA
structure
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.
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:
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);
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 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 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.
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 can be achieved by specifying a list of icons using SetIconList(...)
, with either a range of icon resource IDs, or an array of HICON
s 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.
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.
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:
#define _WIN32_IE 0x0500
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 theNOTIFYICONDATA_V2_SIZE
constant and the pre-5.0 structure is given by theNOTIFYICONDATA_V1_SIZE
constant. Initialize thecbSize
member with:nid.cbSize = NOTIFYICONDATA_V2_SIZE;Using this value for
cbSize
will allow your application to useNOTIFYICONDATA
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.
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.
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.
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.
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.
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 . 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.