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






4.85/5 (19投票s)
2002年6月21日
7分钟阅读

250583

2113
本文介绍了一个辅助类,该类有助于将图标放置在 shell(又称“系统托盘”)中,并更改提示文本。您只需从辅助类派生您的 ATL 对象即可获得此功能。
引言
有时您正在开发一个没有 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 区域看到一个图标出现。
您可以通过单击“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 对象的头文件。
将文件 ShellIconHelper.h 添加到您的工作区。它包含
CShellIconHelper
类。在您的 COM 服务器的头文件中,添加行
#include "ShellIconHelper.h"
。您可能还需要更改 COM 服务器的项目设置,以便支持异常,因为辅助类使用了 STL。转到项目/设置,然后选择“C++ language”类别。选中“Enable exception handling”。如果您真的不想要异常处理,您可以将
ShellIconHelper
类更改为使用LPCSTR
而不是std::string
- 这是一个非常微不足道的更改。将
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>
为此类添加一个消息映射(在其他宏声明的部分),以便您的 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()
宏 - 否则它将无法编译。覆盖此类的
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; }
覆盖此类的
OnFinalRelease
方法。我们在这里添加的代码会在应用程序关闭时从系统托盘中移除图标。注意:您必须包含此部分代码,并调用SetShellVisible (false)
否则您的应用程序在卸载过程中很可能会发生访问冲突。public: void FinalRelease () { SetShellVisible (false); }
为计时器消息添加一个处理程序。此处理程序会定期调用,调用频率是您在调用
SetShellTimer
时指定的。如果您想更改系统托盘图标的图标或文本,您应该调用SetShellIcon
和SetShellTipText
。LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { SetShellIcon (IDI_ICON1); SetShellTipText(std::string("Some text")); return 0; }
为用户命令添加一个处理程序。这使您能够响应系统托盘图标上的鼠标事件。例如,当用户右键单击您的图标时,您可能希望显示一个弹出菜单,如下所示。
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, ¬ifyIconData); }
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 下工作。最后,该类应该支持“气球”提示。