Click here to Skip to main content
15,860,943 members
Articles / Desktop Programming / ATL
Article

Placing an icon in the system tray from an ATL COM server - with minimum hassle

Rate me:
Please Sign up or sign in to vote.
4.72/5 (10 votes)
23 Jun 20027 min read 240.4K   2.1K   57   21
This article describes a helper class that assists with placing an icon in the shell (aka "system tray"), and changing the tip text. You can get this functionality by simply deriving your ATL object from the helper class.

Sample Image - System_Tray.gif

Introduction

Sometimes you are developing an ATL COM server that does not have a GUI, and you would like an icon in the shell (or "system tray" as it's also known), to display some sort of runtime information. I needed this for a project I was working on at the time, and was inspired by the little icon that appears when you connect to the Internet with your modem. It shows you how long you've been connected, and the number of bytes sent and received. The icon also changes when there is activity - a nice touch.

I had a look at some of the source code available on Code Project, but none did exactly what I wanted. For a start most of the ATL servers I was developing did not have a GUI or a window associated with them. I wanted a utility class that I could derive from when creating my ATL COM server that worked out of the box, and that handled all the complexities of creating a hidden window, dealing with menus etc.

So what does this code do? Well this class allows you to display an icon of your choosing in the shell, which can be changed as often as you like. You can update the tip text associated with the icon when you like. You can respond to mouse events that are generated over your tray icon. And, the class will display a popup menu of your choosing on demand.

You might want to check out other articles on Code Project concerning the shell. This is just a very simple class to get support for the system tray into your ATL COM server quickly. If nothing else then it's example code. This code was developed with Visual Studio 6.0, I haven't tested it in Visual Studio .NET.

Usage

Compile the Visual C++ project and make sure it registers the COM server that it builds. This has a minimal COM server with some functions to demonstrate how to use the helper class.

Start the VB demo application ShellTest.exe; you will see an icon appear in the shell area.

System Tray

You can enable or disable the icon by clicking on the "Visible in shell" checkbox. Type some text into the tip edit box and click "Update", and the tip text will change.

Check the "animate" button and the icon will change. You can alter how often the icon animates by entering a valid (say, 100) in the frequency edit box, and then click the update button.

Right click on the shell icon. You will see a context menu appear. Select "About" and a dialog will appear.

Using the CShellIconHelper class

The class has a number of functions that you'll want to call to work with the shell:

virtual void SetShellTipText (std::string &TipText)

Call SetShellTipText to pass in the tip text you want displayed. If you specify more than 64 characters, it will be truncated. This is because the basic version of the common controls only supports this number of characters.

virtual void SetShellIcon (WORD IconResource)

Pass in the resource ID of the icon (e.g. IDI_ICON1) that you want displayed in the shell.

virtual void SetShellTimer (bool bEnabled, WORD wTimerDuration)

Call SetShellTimer to enable a windows timer for your shell icon, if you want to use one. Your ATL server class will then receive WM_TIMER messages, which you can do some processing with. You can also switch off a running timer by calling this with bEnabled = false. The timer duration is specified in milliseconds.

virtual void SetShellVisible (bool bVisible = true)

Calling SetShellVisible with true displays the icon in the shell, and if you have requested to use the timer, starts it off. Calling this with false removes the icon from the shell and stops any timer.

virtual WORD ShowPopupMenu (WORD PopupMenuResource)

Call ShowPopupMenu with the resource ID of your menu (e.g. IDR_POPUP_MENU). It will return back the menu ID that was selected, or zero if no menu item was selected.

How to use this code in your project.

I'm assuming you used the wizard to create an ATL COM server, and have added an ATL object to your project. All the changes you'll need to make are to the header file of your ATL object.

  1. Add the file ShellIconHelper.h to your workspace. This contains the class CShellIconHelper.

  2. In the header file for your COM server, add in the line #include "ShellIconHelper.h".

  3. You might also need to change project settings for your COM server so that exceptions are supported, as the helper class uses STL. Go to project/settings and select "C++ language" category. Check the "Enable exception handling". If you really don't want exception handling then you could just change the ShellIconHelper class to use LPCSTR instead of std::string - quite a trivial change.

  4. Add the class CShellIconHelper to the list of derived classes of your COM server class. As it's a templated class you need to specify the name of the class in angle brackets, in the same fashion as many of the other derived classes listed.

    ////////////////////////////////////////////////
    // CMyServer
    class ATL_NO_VTABLE CMyServer : 
    	public CComObjectRootEx<CComSingleThreadModel>,
    	public CComCoClass<CMyServer, &CLSID_MyServer>,
    	public ISupportErrorInfo,
    	public IDispatchImpl<IMyServer, &IID_IMyServer, 
                                 &LIBID_SYSTEMTRAYDEMOLib>,
    	public CShellIconHelper<CMyServer>
  5. Add a message map to this class (in the section with the other macro declarations), so that your COM server handles windows messages correctly. In this case we want to respond to timer messages and user commands (i.e. when the user moves the mouse over our shell icon).

    BEGIN_MSG_MAP(CMyServer)
        MESSAGE_HANDLER(WM_TIMER, OnTimer)
        MESSAGE_HANDLER(WM_USER, OnUserCommand)
    END_MSG_MAP()

    If you don't want to use the timer or user commands, just leave out the MESSAGE_HANDLER lines. But you must have the BEGIN_MSG_MAP(...) and END_MSG_MAP() macros in your class - otherwise it won't compile.

  6. Override the method OnFinalConstruct in this class. We'll set up the icon in the system tray here. Note, you can choose to display the icon at any time once your server is loaded (or not at all). I chose OnFinalConstruct as it's a handy place to do this.

    public:
        HRESULT FinalConstruct()
        {
            // Configure the shell, then create it by calling SetShellVisible
            SetShellTipText (std::string("Some tip text"));
            SetShellIcon (IDI_ICON1);
            SetShellVisible ();
            SetShellTimer (true, 1000);    
            return S_OK;
        }
  7. Override the method OnFinalRelease in this class. The code we add here removes the icon from the system tray when the application shuts down. Note: You must include this section of code, with the call to SetShellVisible (false) otherwise your application may well get an access violation during unloading.

    public:
        void FinalRelease ()
        {
            SetShellVisible (false);
        }
  8. Add a handler in for the timer messages. This gets called periodically, at the frequency you specified in the call to SetShellTimer. If you want to change the icon or the text for your system tray icon, you should call SetShellIcon and SetShellTipText.

    LRESULT OnTimer(UINT uMsg, WPARAM wParam,
                    LPARAM lParam, BOOL& bHandled)
    {
        SetShellIcon (IDI_ICON1);
        SetShellTipText(std::string("Some text"));
        return 0;
    }
    
  9. Add a handler in for user commands. This allows you to respond to mouse events over your system tray icon. For example, you'll probably want to display a popup menu when the user right clicks your icon, as shown here. The lParam contains the message that Windows has passed you. You call the function ShowPopupMenu, with a Resource ID of a menu you want displayed. This function returns the ID of the menu option selected (as defined in the resource files for your project), or 0 if no menu option is selected.

    LRESULT OnUserCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&bHandled)
    {
        if (lParam == WM_RBUTTONUP) 
        {
            // Show the popup menu.  The return code is the menu 
            // item the user selected, or zero if they didn't 
            // make a choice (i.e. by clicking outside of the popup menu).
            WORD cmd = ShowPopupMenu (IDR_POPUP_MENU);
        }
        return 0;
    }

Detailed explanation of code

template <typename T>
class CShellIconHelper : public CWindowImpl<T>

The class CShellIconHelper derives from the ATL class CWindowImpl - this gives us our hidden window, that we need to use to display the icon and to receive windows messages. If your ATL object already derives from CWindowImpl (maybe it's an ActiveX control), then you can remove this derivation. CWindowImpl gives us a public member m_hWnd - this stores the window handle of our hidden window.

void ShellNotify (DWORD msg)
{
    m_CurrentText = m_CurrentText;
    m_CurrentIconResource = m_CurrentIconResource;

    NOTIFYICONDATA notifyIconData;
    notifyIconData.cbSize = sizeof(notifyIconData);
    notifyIconData.hWnd = m_hWnd;
    notifyIconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
    notifyIconData.uCallbackMessage = WM_USER;
    notifyIconData.uID = 0;

    notifyIconData.hIcon = ::LoadIcon(_Module.GetResourceInstance),
                                      MAKEINTRESOURCE (m_CurrentIconResource));
    ::lstrcpyn(notifyIconData.szTip, m_CurrentText.c_str(), 64);
                                            // Limit to 64 chars
    ::Shell_NotifyIcon (msg, &notifyIconData);
}

ShellNotify does the actual work of updating the shell icon and tip text. It's really quite simple - just fill in the NOTIFYICONDATA structure with the required information and call Shell_NotifyIcon. Note that the shell supports "balloon messages" on certain versions of Windows with the newer version of the common controls. I decided against supporting these, as I wanted the code to run on as many platforms as possible.

virtual WORD ShowPopupMenu (WORD PopupMenuResource)
{
    HMENU hMenu, hPopup = 0;

    hMenu = ::LoadMenu (_Module.GetModuleInstance(),
                        MAKEINTRESOURCE (PopupMenuResource));

    if (hMenu != 0)
    {
        POINT pt;
        ::GetCursorPos (&pt);

        // TrackPopupMenu cannot display the menu bar so get
        // a handle to the first shortcut menu.
        hPopup = ::GetSubMenu (hMenu, 0);

        // To display a context menu for a notification icon, the
        // current window must be the foreground window before the
        // application calls TrackPopupMenu or TrackPopupMenuEx. Otherwise,
        // the menu will not disappear when the user clicks outside of the
        // menu or the window that created the menu (if it is visible).
        ::SetForegroundWindow (m_hWnd);

        WORD cmd = ::TrackPopupMenu (hPopup, TPM_RIGHTBUTTON | TPM_RETURNCMD,
                                     pt.x, pt.y, 0, m_hWnd, NULL);

        // See MS KB article Q135788
        ::PostMessage (m_hWnd, WM_NULL, 0, 0);

        // Clear up the menu, we're not longer using it.
        ::DestroyMenu (hMenu);
        return cmd;
    }
    return 0;
}

The only other noteworthy piece of code is the function ShowPopupMenu, which displays and then tracks a popup menu. Note that it gets a "sub menu" from the menu you have defined, as you cannot display a menu bar. Also it calls SetForegroundWindow before it tracks the pop up menu; this is required so that if you click away from the popup menu without selecting an item it will automatically disappear. You must also post a message to the window after tracking the popup menu; this behavouir is "by design"! The function returns the menu item you selected, or zero if you didn't select anything.

Futher development

I tested this project with an ATL DLL and ATL EXE server. I didn't try with a service - there are other articles on Code Project you might want to look at for services. I also want to get this to work under Visual Studio .NET. Finally the class should support "Balloon" tips.

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
United Kingdom United Kingdom
I first started tinkering with a ZX81 back in 1981 and it's lovely blocky graphics, teaching myself Z80 assembler and BASIC. I come from a hardware background and electronics - I started out in embedded software for avionics but soon moved onto PC platforms.

Java and C++ are my prefered language but also dabble in VB and .NET.

I'm a team leader with Nokia developing Series 40 features - it's a cool place to work!

Comments and Discussions

 
QuestionLicensing question... Pin
Member 1145960112-May-15 7:19
Member 1145960112-May-15 7:19 
Questionhow to add icon in System tray Pin
kalayni11-Jul-08 1:54
kalayni11-Jul-08 1:54 
GeneralMultithreading Pin
Alex (Swiftify)26-Jul-07 10:39
Alex (Swiftify)26-Jul-07 10:39 
Generalabout use Full Control Pin
summercwj14-May-04 17:08
summercwj14-May-04 17:08 
Generalresolve this problem Pin
summercwj15-May-04 2:59
summercwj15-May-04 2:59 
GeneralRe: about use Full Control Pin
Anonymous18-May-04 17:18
Anonymous18-May-04 17:18 
GeneralRe: about use Full Control Pin
Jon Taylor18-May-04 21:44
Jon Taylor18-May-04 21:44 
QuestionHow to close IE Pin
summercwj13-May-04 3:35
summercwj13-May-04 3:35 
GeneralSimilar WTL code Pin
Rob Caldecott25-Apr-04 23:14
Rob Caldecott25-Apr-04 23:14 
GeneralTray Icon for ATL7 Pin
Stefan Diehm28-Aug-03 4:27
Stefan Diehm28-Aug-03 4:27 
GeneralRe: Tray Icon for ATL7 Pin
Jon Taylor8-Oct-03 3:31
Jon Taylor8-Oct-03 3:31 
GeneralRe: Tray Icon for ATL7 Pin
Stefan Diehm8-Oct-03 21:55
Stefan Diehm8-Oct-03 21:55 
GeneralHWND or Location Pin
David Horner30-Jun-03 15:36
David Horner30-Jun-03 15:36 
GeneralRe: HWND or Location Pin
Jon Taylor8-Oct-03 3:37
Jon Taylor8-Oct-03 3:37 
GeneralsYSTEM tRAY icon NOT VISIBLE THRU sERVICE Pin
verinder_bindra23-Jul-02 12:55
verinder_bindra23-Jul-02 12:55 
GeneralRe: sYSTEM tRAY icon NOT VISIBLE THRU sERVICE Pin
TomM19-Aug-03 13:29
TomM19-Aug-03 13:29 
GeneralRe: sYSTEM tRAY icon NOT VISIBLE THRU sERVICE Pin
Manuele Sicuteri2-Apr-04 2:30
Manuele Sicuteri2-Apr-04 2:30 
GeneralIcon resize Pin
slescure9-Jul-02 23:51
slescure9-Jul-02 23:51 
Got a little problem with this code, because the system is using the 32x32 icon, resized, to fit in the 16x16 box in the system tray... Problem is that the system is doing this really bad for sophisticated icons...

I can't figure out how to have my 16x16 image for the icon be taken into account. Do you have a solution?
GeneralRe: Icon resize Pin
Jon Taylor12-Jul-02 0:01
Jon Taylor12-Jul-02 0:01 
GeneralSmall fix Pin
Petr Prazak23-Jun-02 22:03
Petr Prazak23-Jun-02 22:03 
GeneralRe: Small fix Pin
Jon Taylor23-Jun-02 23:36
Jon Taylor23-Jun-02 23:36 

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.