65.9K
CodeProject 正在变化。 阅读更多。
Home

将图标放置在 ATL COM 服务器的系统托盘中 - 轻松实现

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (19投票s)

2002年6月21日

7分钟阅读

viewsIcon

250583

downloadIcon

2113

本文介绍了一个辅助类,该类有助于将图标放置在 shell(又称“系统托盘”)中,并更改提示文本。您只需从辅助类派生您的 ATL 对象即可获得此功能。

Sample Image - System_Tray.gif

引言

有时您正在开发一个没有 GUI 的 ATL COM 服务器,并且希望在 shell(或称为“系统托盘”)中有一个图标来显示某种运行时信息。我在当时的一个项目中需要这个功能,并且受到连接互联网时出现的那个小图标的启发。它显示了您连接的时间,以及发送和接收的字节数。图标在有活动时还会更改 - 一个不错的细节。

我查看了 CSDN 上的一些可用源代码,但没有一个完全符合我的要求。首先,我开发的许多 ATL 服务器都没有 GUI 或与之关联的窗口。我想要一个实用程序类,在创建我的 ATL COM 服务器时可以从中派生,并且该类能够开箱即用,并处理创建隐藏窗口、处理菜单等所有复杂性。

那么这段代码做了什么?这个类允许您在 shell 中显示您选择的图标,您可以随时更改它。您可以随时更新与图标关联的提示文本。您可以响应在您的托盘图标上生成的鼠标事件。而且,该类还可以在需要时显示您选择的弹出菜单。

您可能想查看 CSDN 上其他关于 shell 的文章。这只是一个非常简单的类,用于快速将系统托盘支持添加到您的 ATL COM 服务器中。至少它也是示例代码。此代码是在 Visual Studio 6.0 中开发的,我尚未在 Visual Studio .NET 中进行测试。

用法

编译 Visual C++ 项目,并确保它注册了它构建的 COM 服务器。这有一个最小的 COM 服务器,包含一些函数来演示如何使用辅助类。

启动 VB 演示应用程序ShellTest.exe;您将在 shell 区域看到一个图标出现。

System Tray

您可以通过单击“Visible in shell”复选框来启用或禁用该图标。在提示编辑框中键入一些文本,然后单击“Update”,提示文本将更改。

选中“animate”按钮,图标将更改。您可以通过在频率编辑框中输入一个有效值(例如 100),然后单击更新按钮来更改图标动画的频率。

右键单击 shell 图标。您将看到一个上下文菜单出现。选择“About”,一个对话框将出现。

使用 CShellIconHelper 类

该类有几个您想要调用的函数来与 shell 交互

virtual void SetShellTipText (std::string &TipText)

调用 SetShellTipText 来传递您想要的提示文本。如果您指定的字符数超过 64 个,它将被截断。这是因为通用控件的基本版本仅支持此字符数。

virtual void SetShellIcon (WORD IconResource)

传入您想在 shell 中显示的图标的资源 ID(例如 IDI_ICON1)。

virtual void SetShellTimer (bool bEnabled, WORD wTimerDuration)

调用 SetShellTimer 来为您的 shell 图标启用窗口计时器,如果您想使用它的话。然后,您的 ATL 服务器类将接收 WM_TIMER 消息,您可以使用这些消息进行一些处理。您还可以通过将 `bEnabled` 设置为 false 来关闭正在运行的计时器。计时器持续时间以毫秒为单位。

virtual void SetShellVisible (bool bVisible = true)

调用 SetShellVisible 并将 true 设置为显示 shell 中的图标,如果您已请求使用计时器,则启动它。将此设置为 false 会从 shell 中移除图标并停止任何计时器。

virtual WORD ShowPopupMenu (WORD PopupMenuResource)

调用 ShowPopupMenu 并传入您的菜单的资源 ID(例如 IDR_POPUP_MENU)。它将返回所选菜单的 ID,如果没有选择菜单项,则返回零。

如何在您的项目中引用此代码。

我假设您使用了向导来创建 ATL COM 服务器,并在项目中添加了一个 ATL 对象。您需要做的所有更改都将针对您 ATL 对象的头文件。

  1. 将文件 ShellIconHelper.h 添加到您的工作区。它包含 CShellIconHelper 类。

  2. 在您的 COM 服务器的头文件中,添加行 #include "ShellIconHelper.h"

  3. 您可能还需要更改 COM 服务器的项目设置,以便支持异常,因为辅助类使用了 STL。转到项目/设置,然后选择“C++ language”类别。选中“Enable exception handling”。如果您真的不想要异常处理,您可以将 ShellIconHelper 类更改为使用 LPCSTR 而不是 std::string - 这是一个非常微不足道的更改。

  4. CShellIconHelper 类添加到您的 COM 服务器类的派生类列表中。由于它是一个模板类,您需要在尖括号中指定类名,与列出的许多其他派生类相同。

    ////////////////////////////////////////////////
    // 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. 为此类添加一个消息映射(在其他宏声明的部分),以便您的 COM 服务器能够正确处理窗口消息。在这种情况下,我们希望响应计时器消息和用户命令(即当用户将鼠标悬停在我们的 shell 图标上时)。

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

    如果您不想使用计时器或用户命令,只需省略 MESSAGE_HANDLER 行。但您的类中必须包含 BEGIN_MSG_MAP(...)END_MSG_MAP() 宏 - 否则它将无法编译。

  6. 覆盖此类的 OnFinalConstruct 方法。我们将在那里设置系统托盘中的图标。请注意,您可以在服务器加载后随时显示图标(或不显示)。我选择了 OnFinalConstruct,因为它是一个方便的设置点。

    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. 覆盖此类的 OnFinalRelease 方法。我们在这里添加的代码会在应用程序关闭时从系统托盘中移除图标。注意:您必须包含此部分代码,并调用 SetShellVisible (false) 否则您的应用程序在卸载过程中很可能会发生访问冲突。

    public:
        void FinalRelease ()
        {
            SetShellVisible (false);
        }
  8. 为计时器消息添加一个处理程序。此处理程序会定期调用,调用频率是您在调用 SetShellTimer 时指定的。如果您想更改系统托盘图标的图标或文本,您应该调用 SetShellIconSetShellTipText

        LRESULT OnTimer(UINT uMsg, WPARAM wParam,
                        LPARAM lParam, BOOL& bHandled)
        {
    		SetShellIcon (IDI_ICON1);
    		SetShellTipText(std::string("Some text"));
    		return 0;
        }
  9. 为用户命令添加一个处理程序。这使您能够响应系统托盘图标上的鼠标事件。例如,当用户右键单击您的图标时,您可能希望显示一个弹出菜单,如下所示。lParam 包含 Windows 传递给您的消息。您调用 ShowPopupMenu 函数,并传入您想要显示的菜单的资源 ID。此函数返回所选菜单项的 ID(如项目资源文件中定义的),或者在未选择任何菜单项时返回 0。

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

代码详细解释

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

CShellIconHelper 类派生自 ATL 类 CWindowImpl - 这为我们提供了隐藏窗口,我们需要使用该窗口来显示图标并接收窗口消息。如果您的 ATL 对象已经派生自 CWindowImpl(可能是 ActiveX 控件),那么您可以移除此派生。CWindowImpl 为我们提供了一个公共成员 m_hWnd - 它存储了我们隐藏窗口的窗口句柄。

    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 执行更新 shell 图标和提示文本的实际工作。它确实很简单——只需用所需信息填充 NOTIFYICONDATA 结构并调用 Shell_NotifyIcon。请注意,在较新版本的通用控件的某些 Windows 版本上,shell 支持“气球消息”。我决定不支持这些,因为我希望代码能够在尽可能多的平台上运行。

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

唯一其他值得注意的代码部分是 ShowPopupMenu 函数,该函数显示然后跟踪一个弹出菜单。请注意,它从您定义的菜单中获取“子菜单”,因为您无法显示菜单栏。它还在跟踪弹出菜单之前调用 SetForegroundWindow;这是必需的,因此如果您在未选择项目的情况下单击弹出菜单以外的区域,它将自动消失。您还必须在跟踪弹出菜单后向窗口发送一条消息;此行为是“按照设计”的!该函数返回您选择的菜单项,如果您什么都没选择则返回零。

进一步开发

我使用 ATL DLL 和 ATL EXE 服务器测试了这个项目。我没有尝试使用服务 - CSDN 上还有其他文章可能对您有帮助。我还想让它在 Visual Studio .NET 下工作。最后,该类应该支持“气球”提示。

© . All rights reserved.