Click here to Skip to main content
15,883,883 members
Articles / Desktop Programming / ATL

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 241.1K   2.1K   57  
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.
// Shell Icon Helper - by Jon Taylor, for Code Project
// See documentation on Code Project web site for information on how 
// to use this class.

#ifndef __SHELLICONHELPER_H
#define __SHELLICONHELPER_H

#include <string>
#include <atlwin.h>

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

    virtual HRESULT CreateShell ()
    {
        RECT rect;
        rect.left = rect.right = rect.top = rect.bottom = 0;

        // Create a hidden window (using CWindowImpl)
        HWND hWnd = Create (NULL, rect, "ShellIconHiddenWindow", WS_POPUP);

        if (hWnd != 0) // Was created?
        {
            // Add the icon into the shell
            ShellNotify (NIM_ADD);
            return S_OK;
        }
        else return HRESULT_FROM_WIN32 (GetLastError());
    }
      
    virtual void DestroyShell ()
    {
        ShellNotify (NIM_DELETE); // Remove the icon
        if (m_hWnd != NULL)
        {
            // Get rid of the hidden window
            DestroyWindow();
        }
    }

    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);    
    }
public:

    CShellIconHelper () 
    {
        // Initialise internal variables to known good state
        m_bVisible = false;
        m_bTimerActive = false;
        m_TimerId = 0;
        m_wTimerDuration = 0;
        m_CurrentIconResource = 0;
        m_CurrentText = std::string("");
    }
    
    virtual void SetShellTipText (std::string &TipText)
    {
        // Save this text for when we update
        m_CurrentText = TipText;
        ShellNotify (NIM_MODIFY);
    }

    virtual void SetShellIcon (WORD IconResource)
    {
        // Save this icon resource for when we update
        m_CurrentIconResource = IconResource;
        ShellNotify (NIM_MODIFY);
    }

    virtual void SetShellTimer (bool bEnabled, WORD wTimerDuration = 1000)
    {
        if (bEnabled == true) // User wants to start a timer
        {
            if (m_bTimerActive = true)
            {
                ::KillTimer (m_hWnd, m_TimerId);
            }
            // Start the timer
            m_TimerId = ::SetTimer (m_hWnd, 1, wTimerDuration, NULL);
        }
        else // User wants to shut down the timer
        {
            if (m_bTimerActive = true)
            {
                ::KillTimer (m_hWnd, m_TimerId);
            }
        }
        m_wTimerDuration = wTimerDuration;
        m_bTimerActive = bEnabled;
    }

    virtual void SetShellVisible (bool bVisible = true)
    {
        if (bVisible == true) // User wants to show the icon in the shell
        {
            if (m_bVisible == false) // Doesn't already exist?
            {
                // Create the shell, and timer (if applicable)
                CreateShell ();
            } // Otherwise, well you already have icon in the shell. :-)

            SetShellTimer (m_bTimerActive, m_wTimerDuration);

        }
        else // User wants rid of the icon
        { 
            if (m_bVisible == true)  // Is it there already?
            {
                // Destroy any running timer
                if (m_bTimerActive == true)
                {
                    ::KillTimer (m_hWnd, m_TimerId);
                }
                DestroyShell (); // Get rid
            }
        }

        m_bVisible = bVisible;
    }

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



private:

    bool m_bVisible;
    bool m_bTimerActive;
    UINT_PTR m_TimerId;
    WORD    m_wTimerDuration;

    int m_CurrentIconResource;
    std::string m_CurrentText;
};

#endif


By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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