S.I.V.: Simple tray icon implementation






4.54/5 (16 votes)
Jan 27, 2004
5 min read

73912

1773
Simplicity Is Virtue: create your own tray icon class in a VERY simple manner.
Introduction
Welcome to my second article of Simplicity Is Virtue philosophy. Never heard of it? Of course, I created it.. (-: (If you're really interested what the heck is that, then take a look at my first article.) This article is aimed towards beginners, so all of you coding gurus and wizards can skip all of this. In this article, you can learn how to:
- create, modify and remove a tray icon
- define and use home-made window messages
Before we begin, let me explain why create YET ANOTHER tray icon class, when there are so many of them, free for use. Well, I was browsing the net, searching for a tray icon class, just being lazy of typing my own code. I actually found a whole bunch of them, free, powerful and quite often - loaded with features I don't need. That means - very irritating to use. So I said to myself, Today IS a good day to die, and started coding the most simple tray icon class anyone could ever think of :-). Let us begin.
Preparations
Before we do anything, let us recall: you create a tray icon by creating a NOTIFYICONDATA
structure, and giving it to the Shell_NotifyIcon
API function. That tends to be quite irritating, especially if you change the icon or the tooltip frequently during runtime. Because of that, we will create a new class, and provide ourselves a simple and clean interface.
First, we create a new dialog based project, all other options are irrelevant. We begin by adding a new generic class to our project. I named mine "CSimpleTray
". Now, we will take care of the private class variables: we need a single icon, a single tooltip, and a single NOTIFYICONDATA
structure. Besides that, we would need an indicator whether our icon is shown or not, so we will create a BOOL
variable called m_bEnabled
. The class should look like this by now:
// SimpleTray.h class CSimpleTray { public: CSimpleTray(); virtual ~CSimpleTray(); private: BOOL m_bEnabled; HICON m_hIcon; NOTIFYICONDATA m_nid; CString m_strTooltip; };
Now let's see: besides showing and hiding the icon, we want to be able to change the icon and tooltip during runtime, and basically that's all we will do here. Simple interface will consist only of these functions:
void Show();
void Hide();
void SetIcon( HICON hIcon );
void SetTooltip( LPCTSTR lpTooltip );
Still, there is only one thing to think of: we want our icon to actually DO something when our user clicks on it. To implement that, we will make our icon send a notification to the app's main dialog, simply by sending a custom window message. First things first, we will define the message before our tray icon class declaration, like this:
// SimpleTray.h // at the beggining of the file, before class declaration #define WM_TRAYNOTIFY WM_APP+1
Now, we need to mess a bit with the dialog itself, add a new protected function declared like this:
afx_msg void OnTrayNotify( WPARAM wParam, LPARAM lParam );
Ok, so now that we have #define
d a window message and a function, we need to connect them. You do this by manually adding an entry to the framework's message map:
// TrayDemoDlg.cpp BEGIN_MESSAGE_MAP(CTrayDemoDlg, CDialog) //{{AFX_MSG_MAP(CTrayDemoDlg) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_MESSAGE(WM_TRAYNOTIFY, OnTrayNotify) // <-- add this line //}}AFX_MSG_MAP END_MESSAGE_MAP()
Note that there is no semicolon at the end of the line. Now, every time our dialog receives WM_TRAYNOTIFY
message, it will call the message we created - OnTrayNotify
. Of course, you will need to #include
the class declaration file, otherwise the compiler will complain that WM_TRAYNOTIFY
is not defined.
// TrayDemoDlg.cpp // at the beggining of the file #include "stdafx.h" #include "TrayDemo.h" #include "TrayDemoDlg.h" #include "SimpleTray.h" // <-- this one
Finally - the class..
Finally, we get to code the class. Let us check out the code:
CSimpleTray::CSimpleTray() { // initialize m_hIcon = NULL; m_strTooltip = ""; m_bEnabled = FALSE; } CSimpleTray::~CSimpleTray() { Hide(); // a good thing to do :-) } void CSimpleTray::Show() { // check to see if we have a valid icon if ( m_hIcon == NULL ) return; // make sure we are not already visible if ( m_bEnabled == TRUE ) return; // initialize the TRAYNOTIFYDATA struct m_nid.cbSize = sizeof(m_nid); m_nid.hWnd = AfxGetMainWnd()->m_hWnd; // the 'owner' window // this is the window that // receives notifications m_nid.uID = 100; // ID of our icon, as you can have // more than one icon in a single app m_nid.uCallbackMessage = WM_TRAYNOTIFY; // our callback message strcpy(m_nid.szTip, m_strTooltip); // set the tooltip m_nid.hIcon = m_hIcon; // icon to show m_nid.uFlags = NIF_MESSAGE | // flags are set to indicate what NIF_ICON | // needs to be updated, in this case NIF_TIP; // callback message, icon, and tooltip Shell_NotifyIcon( NIM_ADD, &m_nid ); // finally add the icon m_bEnabled = TRUE; // set our indicator so that we // know the current status } void CSimpleTray::Hide() { // make sure we are enabled if ( m_bEnabled == TRUE ) { // we are, so remove m_nid.uFlags = 0; Shell_NotifyIcon( NIM_DELETE, &m_nid ); // and again, we need to know what's going on m_bEnabled = FALSE; } } void CSimpleTray::SetIcon(HICON hIcon) { // first make sure we got a valid icon if ( hIcon == NULL ) return; // set it as our private m_hIcon = hIcon; // now check if we are already enabled if ( m_bEnabled == TRUE ) { // we are, so update m_nid.hIcon = m_hIcon; m_nid.uFlags = NIF_ICON; // icon only Shell_NotifyIcon( NIM_MODIFY, &m_nid ); } } void CSimpleTray::SetTooltip(LPCTSTR lpTooltip) { // tooltip can be empty, we don't care m_strTooltip = lpTooltip; // if we are enabled if ( m_bEnabled == TRUE ) { // do an update strcpy(m_nid.szTip, m_strTooltip); m_nid.uFlags = NIF_TIP; // tooltip only Shell_NotifyIcon( NIM_MODIFY, &m_nid ); } }
That's it!
How to use it?
The most simple way would be to use your applications icon, and show the damn thing at the program startup, inside OnInitDialog()
. First, add a private variable of type CSimpleTray
to the dialog class:
and then use it like this:
// TrayDemoDlg.cpp //inside OnInitDialog() // TODO: Add extra initialization here m_pTrayIcon.SetIcon( m_hIcon ); // application's icon m_pTrayIcon.SetTooltip( "Look at me!" ); m_pTrayIcon.Show(); //...
All you need to do now, is set the icon's behavior, and you do that inside OnTrayNotify
. For example, we want to display a menu when a user right clicks on our icon. I created mine like this:
If you press CTRL+W to open the class wizard, you will get a dialog box with two options: to create a new class for your menu, or to select an existing one. Choose "Select an existing class" and press OK. On the next dialog, you can see a list of classes available in your project. The most appropriate class would be your main dialog class, in this case CTrayDemoDlg
. Select it and press OK. Now you can edit the code for your popup menu, and it will be inside CTrayDemoDlg
class.
Now, let us edit our callback function:
// TrayDemoDlg.cpp void CTrayDemoDlg::OnTrayNotify(WPARAM wParam, LPARAM lParam) { // switch lParam, which contains the event that occured // wParam would hold the icon's ID, but since we have // only one icon, we don't need to check that switch ( lParam ) { case WM_LBUTTONDOWN: { // left click, activate window // we would do here the same thing as if // the user clicked the "show dialog" menu // so don't duplicate the code OnShowdialog(); } break; case WM_RBUTTONUP: { // right click, show popup menu CMenu temp; CMenu *popup; CPoint loc; GetCursorPos( &loc ); temp.LoadMenu( IDR_MNUPOPUP ); popup = temp.GetSubMenu( 0 ); popup->TrackPopupMenu( TPM_LEFTALIGN, loc.x, loc.y, this ); } break; default: // do nothing break; } }
We also might want to hide the taskbar button of your dialog, and we can do it inside OnInitDialog()
. The demo app demonstrates how to accomplish that, and how to implement a simple "animation" - a flashing tray icon.
THE END - I hope someone will find this article useful in some way! (-:
Copyright
As for the copyright, all of this is copyrighted by me (T1TAN) and my pseudo-company SprdSoft Inc. That means that you can use this code in any app (private or commercial) without any restrictions whatsoever, since our company supports open source and free software. However, if you are going to use (parts of) the code, I'd like to hear from you, just to keep track of things.
If you have any comments, ideas or anything else about the article/code, feel free to contact me, I'd be glad to hear from you.
Greets & good programming!
History
- 25/01/2004 - First release.