Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

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

0.00/5 (No votes)
23 Jun 2002 1  
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