Click here to Skip to main content
15,868,141 members
Articles / Desktop Programming / MFC
Article

S.I.V.: Simple tray icon implementation

Rate me:
Please Sign up or sign in to vote.
4.54/5 (17 votes)
26 Jan 20045 min read 72.5K   1.8K   25   17
Simplicity Is Virtue: create your own tray icon class in a VERY simple manner.

Demo app screenshot

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

Adding a new protected function

Ok, so now that we have #defined 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:

Adding a tray icon variable

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:

Popup menu design

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Croatia Croatia
A software developer for the masses..

Comments and Discussions

 
QuestionForce a tooltip message to popup Pin
matt20005-Mar-07 4:18
matt20005-Mar-07 4:18 
AnswerRe: Force a tooltip message to popup Pin
T1TAN5-Mar-07 4:40
T1TAN5-Mar-07 4:40 
GeneralRe: Force a tooltip message to popup Pin
matt20005-Mar-07 5:54
matt20005-Mar-07 5:54 
GeneralRe: Force a tooltip message to popup Pin
T1TAN5-Mar-07 6:03
T1TAN5-Mar-07 6:03 
GeneralTooltip length bug Pin
AVIDeveloper13-Jun-06 1:30
AVIDeveloper13-Jun-06 1:30 
GeneralRe: Who help me to do it step by step? Pin
T1TAN24-Jan-05 6:56
T1TAN24-Jan-05 6:56 
QuestionHow to make window active window when tray icon clicked ? Pin
Defenestration4-Dec-04 3:20
Defenestration4-Dec-04 3:20 
AnswerRe: How to make window active window when tray icon clicked ? Pin
T1TAN8-Dec-04 23:27
T1TAN8-Dec-04 23:27 
GeneralGreat class! Minor bug Pin
Damir Valiulin23-Apr-04 6:26
Damir Valiulin23-Apr-04 6:26 
GeneralRe: Great class! Minor bug Pin
T1TAN24-Apr-04 8:29
T1TAN24-Apr-04 8:29 
hi damir,

Damir Valiulin wrote:
the functionality of just Show/Hide/Tooltip/Icon is all I needed for my little project.

i know what you mean Roll eyes | :rolleyes:

Damir Valiulin wrote:
One small bug I found is that there's memory access violation if the Tooltip string passed to SetTooltip is greater than the szTip buffer (which is a member of NOTIFYICONDATA structure).

yep, true, it is a bug, but i did it on purpose Wink | ;) i knew that the length of the tool tip *must* be limited, but i simply ignored it, since this class is just - basic.

there are _many_ other things to improve in this class, but i'd like to keep it this small, makes me happyBig Grin | :-D Cool | :cool:

Damir Valiulin wrote:
I was getting memory access violations

i'm glad you're having fun with the class;PRose | [Rose]

---
kick ash.
http://sprdsoft.cmar-net.org (brand new!)
Generalpopup stuck Pin
kce19008-Apr-04 23:36
kce19008-Apr-04 23:36 
GeneralRe: popup stuck Pin
T1TAN10-Apr-04 8:38
T1TAN10-Apr-04 8:38 
GeneralRe: popup stuck Pin
kce190011-Apr-04 17:17
kce190011-Apr-04 17:17 
GeneralRe: popup stuck Pin
T1TAN12-Apr-04 5:48
T1TAN12-Apr-04 5:48 
GeneralRe: popup stuck FIXED Pin
kce190012-Apr-04 20:13
kce190012-Apr-04 20:13 
GeneralJust one little bug Pin
Cactus Joe10-Mar-04 5:14
Cactus Joe10-Mar-04 5:14 
GeneralRe: Just one little bug Pin
T1TAN12-Mar-04 8:12
T1TAN12-Mar-04 8:12 

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.