将图标添加到系统托盘






4.90/5 (130投票s)
用于将图标添加到系统托盘的类。
![]() ![]() ![]() 在 Windows 9x, Windows CE 和 Windows 2000 中的示例图片 |
引言
然后,当你开始迭代 2(这是构建迭代的开始)时,你可能想要复制测试用例并将它们重新分类到迭代 2。这还允许对测试用例进行粒度跟踪,并允许你说某个测试用例在一个迭代中是准备好的,但在另一个迭代中不是。同样,如何做到这一点取决于你以及你希望如何报告。 “场景”部分提供了更多细节。
操作
图标
将应用程序最小化到系统托盘
默认消息处理
使用示例
关于 TrackPopupMenu 的注意事项
历史
最新
引言
CSystemTray
综合了来自 MSJ 的“Webster”应用程序、在线文档、其他实现(如 PJ Naughter 的 "CTrayNotifyIcon" (http://indigo.ie/~pjn/ntray.html))以及许多其他开发者的贡献。
这个类是对 Windows 系统托盘相关功能的轻量级封装。它向系统托盘添加一个图标,并附带指定的工具提示文本和回调通知值,该值会发送回父窗口。
旧方法
通过 Windows API 使用托盘图标的基本步骤是:
- 加载
NOTIFYICONDATA
结构体 - 调用
Shell_NotifyIcon(NIM_ADD, &MyTrayNotifyStruct)
通过更改 NOTIFYICONDATA
结构体中的字段值并调用 Shell_NotifyIcon
,您可以更改图标或工具提示文本,或者移除图标本身。所有这些繁琐的操作都已封装在一个类中,以使其更简单、更整洁。
更好的方法
向系统托盘添加图标的更简单方法是,创建一个 CSystemTray
类型的对象,可以作为成员变量或动态创建。构造函数提供了两种形式,允许程序员在创建 CSystemTray
对象时将图标插入托盘,或者使用成员函数 CSystemTray::Create
。例如:
CSystemTray m_TrayIcon; // Member variable of some class ... // in some member function maybe... m_TrayIcon.Create(pParentWnd, WM_MY_NOTIFY, "Click here", hIcon, nTrayIconID);
这将在系统托盘中插入一个图标。详情请参见下一节。
是否使用 MFC...
这个类有两种形式:MFC 和非 MFC。它们分别在 SystemTray.* 和 SystemTraySDK.* 文件中。MFC 版本是为使用 MFC 类(CWnd
等)编写的,而非 MFC 版本将使用 HWND
。将来我可能会修改它们,使两者都使用 HWND
以获得最佳兼容性。
两个类功能基本相同,只是非 MFC 版本每个应用程序只支持一个托盘图标。因为非 MFC 版本不是从 CWnd
派生的,您需要小心选择接收图标消息的窗口。如果将图标的父窗口设置为 NULL,那么托盘图标将处理自己的托盘通知消息——但也会尝试处理从图标上下文菜单发送的菜单命令。要解决这个问题,您需要:
- 将托盘图标的父窗口设置为一个能够处理所有托盘图标通知的窗口,或者
- 将父窗口设置为 NULL,并使用
CSystemTray::SetTargetWnd
来指定接收菜单命令的窗口。
例如,对于一个非 MFC 托盘图标,执行以下操作:
CSystemTray m_TrayIcon; // Member variable of some class ... // in some member function maybe... m_TrayIcon.Create(hInstance, NULL, WM_MY_NOTIFY, "Click here", hIcon, nID); // Send all menu messages to hMyMainWindow m_TrayIcon.SetTargetWnd(hMyMainWindow);
然后,当你开始迭代 2(这是构建迭代的开始)时,你可能想要复制测试用例并将它们重新分类到迭代 2。这还允许对测试用例进行粒度跟踪,并允许你说某个测试用例在一个迭代中是准备好的,但在另一个迭代中不是。同样,如何做到这一点取决于你以及你希望如何报告。 “场景”部分提供了更多细节。
CSystemTray(); CSystemTray(CWnd* pWnd, UINT uCallbackMessage, LPCTSTR szToolTip, HICON icon, UINT uID, BOOL bHidden = FALSE, LPCTSTR szBalloonTip = NULL, LPCTSTR szBalloonTitle = NULL, DWORD dwBalloonIcon = NIIF_NONE, UINT uBalloonTimeout = 10); BOOL Create(CWnd* pWnd, UINT uCallbackMessage, LPCTSTR szToolTip, HICON icon, UINT uID, BOOL bHidden = FALSE, LPCTSTR szBalloonTip = NULL, LPCTSTR szBalloonTitle = NULL, DWORD dwBalloonIcon = NIIF_NONE, UINT uBalloonTimeout = 10);
非 MFC 版本包含一个额外的参数(参数 1),它代表应用程序的实例句柄。
请注意,析构函数会自动从托盘中移除图标。
pWnd |
通知消息将发送到的窗口。可以为 NULL |
uCallbackMessage |
将发送到父窗口的通知消息 |
szToolTip |
托盘图标的工具提示 |
icon |
要显示的图标的句柄 |
uID |
托盘图标 ID |
bHidden |
如果为 TRUE,图标初始时是隐藏的 |
szBalloonTip |
气球提示文本(仅限 Windows 2000) |
szBalloonTitle |
气球提示标题(仅限 Windows 2000) |
dwBalloonIcon |
气球提示图标(仅限 Windows 2000) |
uBalloonTimeout |
气球提示超时时间(仅限 Windows 2000) |
如果 pWnd 参数为 NULL
,那么每当图标发送通知消息时,函数 CSystemTray::OnTrayNotification
将被调用。更多详情请参见默认消息处理。
操作
LRESULT OnTrayNotification(WPARAM wID, LPARAM lEvent) // Discussed below void MoveToRight() // Moves the icon to the far right of the tray, // so it is immediately to the left of the clock void HideIcon() // Hides but does not totally remove the icon // from the tray. void RemoveIcon() // Removes the icon from the tray (icon can no // longer be manipulated) void ShowIcon() // Redisplays a previously hidden icon void AddIcon() // Adds the icon to the tray void SetFocus( // Sets the focus to the icon (Win2000 only) BOOL ShowBalloon(LPCTSTR szText, // Shows the balloon tip (Win2000 only) LPCTSTR szTitle = NULL, DWORD dwIcon = NIIF_NONE, UINT uTimeout = 10); BOOL SetTooltipText(LPCTSTR pszTip) // Set Tooltip text BOOL SetTooltipText(UINT nID) // Set tooltip from text resource ID CString GetTooltipText() const // Retrieve tool tip text BOOL SetNotificationWnd(CWnd* pWnd) // Self explanatory CWnd* GetNotificationWnd() const BOOL SetTargetWnd(CWnd* pTargetWnd);// Change or retrieve the window to CWnd* GetTargetWnd() const; // send menu commands to BOOL SetCallbackMessage(UINT uCallbackMessage) // Self explanatory UINT GetCallbackMessage() const; HICON GetIcon() const // Get current tray icon BOOL SetIcon(HICON hIcon) // Change tray icon. Returns FALSE // if unsuccessful BOOL SetIcon(LPCTSTR lpszIconName) // Same, using name of the icon resource BOOL SetIcon(UINT nIDResource) // Same, using icon resource ID BOOL SetStandardIcon(LPCTSTR lpIconName) // Load icon from the BOOL SetStandardIcon(UINT nIDResource) // current application // Set list of icons for animation BOOL SetIconList(UINT uFirstIconID, UINT uLastIconID); BOOL SetIconList(HICON* pHIconList, UINT nNumIcons); // Start animation BOOL Animate(UINT nDelayMilliSeconds, int nNumSeconds = -1); BOOL StepAnimation(); // Step to next icon BOOL StopAnimation(); // Stop animation BOOL SetMenuDefaultItem(UINT uItem, BOOL bByPos); // Set default menu item void GetMenuDefaultItem(UINT& uItem, BOOL& bByPos); // Get default menu item static void MinimiseToTray(CWnd* pWnd); static void MaximiseFromTray(CWnd* pWnd, CRect rectTo); static void MaximiseFromTray(CWnd* pWnd, LPCREATESTRUCT lpCreateStruct); virtual void CustomizeMenu(CMenu*) // Customise the context menu before it's // displayed
SetStandardIcon
也可以接受以下任何值(在 WinCE 中不可用):
nIDResource | 描述 |
IDI_APPLICATION |
默认应用程序图标。 |
IDI_ASTERISK |
星号(用于信息性消息)。 |
IDI_EXCLAMATION |
感叹号(用于警告消息)。 |
IDI_HAND |
手形图标(用于严重警告消息)。 |
IDI_QUESTION |
问号(用于提示性消息)。 |
IDI_WINLOGO |
Windows 徽标 |
默认的 CSystemTray
消息通知处理程序会搜索并显示与托盘图标 ID 相同的菜单。如果您使用这个默认处理程序,可以使用 SetMenuDefaultItem
来设置默认菜单项。参数 uItem 和 bByPos 与 ::SetMenuDefaultItem
中使用的参数相同。
默认菜单项的代码由 Enrico Lelina 贡献。
将应用程序最小化到系统托盘
提供了两个函数,让您可以轻松地将应用程序“最小化”到系统托盘:
static void MinimiseToTray(CWnd* pWnd); static void MaximiseFromTray(CWnd* pWnd);
其中 pWnd 是要最小化或最大化的窗口(通常是您的主应用程序窗口)。
“最小化到系统托盘”意味着使用 DrawAnimatedRects
函数将应用程序主窗口最小化,使其看起来像是折叠进了系统托盘。然后,主应用程序窗口变为不可见,并且应用程序图标从任务栏中移除。要将应用程序最小化到系统托盘,只需调用 MinimiseToTray
。系统托盘最小化的灵感来自 Santosh Rao。
如果应用程序已最小化到托盘,可以通过调用 MaximiseFromTray
再次将其最大化。例如,如果您有一个派生自 CDialog
的类,在用户双击托盘图标(或从弹出菜单中选择一个菜单项)时显示,那么请在您的 CDialog
类中重写 OnDestroy
和 OnShowWindow
函数,并添加以下代码行:
void CMyDialog::OnDestroy() { CDialog::OnDestroy(); CSystemTray::MinimiseToTray(this); } void CMyDialog::OnShowWindow(BOOL bShow, UINT nStatus) { CDialog::OnShowWindow(bShow, nStatus); if (bShow) CSystemTray::MaximiseFromTray(this); }
图标动画
可以通过使用 SetIconList(...)
指定一个图标列表来实现图标动画,可以使用一个图标资源 ID 范围,或者一个 HICON
数组及其大小。然后调用 Animate(UINT nDelayMilliSeconds, int nNumSeconds)
。第一个参数是每帧动画之间的延迟(以毫秒为单位),第二个参数是图标动画的持续秒数。如果指定为 -1,动画将持续进行,直到调用 StopAnimation()
。图标动画的建议由 Joerg Koenig 提出。
默认消息处理
父窗口在接收到通知消息后,可以通过调用 CSystemTray::OnTrayNotification(...)
将此消息重定向回托盘图标进行处理。或者,如果 CSystemTray
对象创建时父窗口为 NULL,则每当图标发送通知时都会调用此函数。默认实现会尝试查找与托盘图标具有相同资源 ID 的菜单。如果找到菜单并且接收到的事件是鼠标右键抬起,则将子菜单显示为上下文菜单。如果接收到双击事件,则将子菜单中第一项的消息 ID 发送回父窗口。
使用新的 Windows 2000 / IE5 气球提示
新的气球提示依赖于 Shell32.dll 版本为 5.0 或更高,并且您的源代码中 _WIN32_IE
被定义为 0x0500 或更高。您可以使用两种方法:
- 假设您的用户将安装 5.0 版本,并添加
#define _WIN32_IE 0x0500
- 或者正确地做:使用 DllGetVersion 获取 Shell32.dll 的版本,并相应地调整您的代码。感谢 Porgee 引用了 MSDN 的提示。
使用
DllGetVersion
函数来确定系统上安装的 Shell32.dll 版本。如果版本是 5.0 或更高,用以下方式初始化 cbSize 成员:nid.cbSize = sizeof(NOTIFYICONDATA);
将 cbSize 设置为这个值可以启用所有版本 5.0 和 6.0 的增强功能。对于早期版本,6.0 之前的
结构体的大小由NOTIFYICONDATA_V2_SIZE
常量给出,5.0 之前的结构体由NOTIFYICONDATA_V1_SIZE
常量给出。用以下方式初始化cbSize
成员:nid.cbSize = NOTIFYICONDATA_V2_SIZE;
使用这个
cbSize
值将允许您的应用程序在早期版本的 Shell32.dll 中使用NOTIFYICONDATA
,尽管没有版本 6.0 的增强功能。
我目前使用的是懒人方法。在系统托盘头文件中有一个定义 ASSUME_IE5_OR_ABOVE
,它决定是否应该假定存在 5.0 或更高版本。对于针对 Shell32.dll 版本低于 5.0 的应用程序,请注释掉此定义。
使用示例
声明托盘图标的好地方是在您派生自 CFrameWnd
的类中。
例如:
#define WM_ICON_NOTIFY WM_APP+10
CSystemTray m_TrayIcon
为托盘图标通知添加一个消息映射条目
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ... ON_MESSAGE(WM_ICON_NOTIFY, OnTrayNotification) END_MESSAGE_MAP()
创建图标(可能在您的 OnCreate 函数中)
if (!m_TrayIcon.Create(this, WM_ICON_NOTIFY, strToolTip, hIcon, IDR_POPUP_MENU)) return -1;
其中 IDR_POPUP_MENU
是要为图标显示的弹出菜单的 ID。然后您需要一个托盘图标通知消息的处理程序:
LRESULT CMainFrame::OnTrayNotification(WPARAM wParam, LPARAM lParam) { // Delegate all the work back to the default // implementation in CSystemTray. return m_TrayIcon.OnTrayNotification(wParam, lParam); }
使用的菜单(IDR_POPUP_MENU
)如下所示:
IDR_POPUP_MENU MENU PRELOAD DISCARDABLE BEGIN POPUP "POPUP_MENU" BEGIN MENUITEM "&About...", ID_APP_ABOUT MENUITEM SEPARATOR MENUITEM "&Options...", ID_APP_OPTIONS MENUITEM SEPARATOR MENUITEM "E&xit", ID_APP_EXIT END END
在托盘图标上单击右键将弹出此菜单,而双击将向框架发送一个 ID_APP_ABOUT
消息。(注意,在 CE 中,ALT+左键将弹出菜单,ALT+双击将执行默认操作)。
或者,您可以使用 m_TrayIcon.Create(NULL, ...)
并省略 WM_ICON_NOTIFY
的消息映射条目。CSystemTray
的默认实现将处理其余部分。
关于 TrackPopupMenu 的注意事项
许多人在使用 TrackPopupMenu
时遇到了麻烦。他们报告说,即使将 TrackPopupMenu()
的最后一个参数设置为 NULL,当鼠标在菜单外部点击时,弹出菜单通常也不会消失。这是微软的一个“特性”,是设计使然。真是令人费解,不是吗?
无论如何,要解决这个“特性”,必须在调用 TrackPopupMenu
之前将当前窗口设置为前景窗口。但这又导致了第二个问题——即下次显示菜单时,它会显示然后立即消失。要解决这个问题,您必须在菜单消失后使当前应用程序处于活动状态。这可以通过向当前窗口发送一个无害的消息(如 WM_NULL)来完成。
所以,本应简单的一行代码:
TrackPopupMenu(hSubMenu, TPM_RIGHTBUTTON, pt.x,pt.y, 0, hDlg, NULL);
变成
SetForegroundWindow(hDlg); TrackPopupMenu(hSubMenu, TPM_RIGHTBUTTON, pt.x,pt.y, 0, hDlg, NULL); PostMessage(hDlg, WM_NULL, 0, 0);
更多信息请参考知识库文章“PRB: Menus for Notification Icons Don't Work Correctly”。
历史
我更新了该类,使其能在 CE 中工作。现在包含了一个 CE 演示项目。
Thomas Mooney 帮助进行了修改,使得该类可以在 NT 服务中使用。问题出现在将 CSystemTray
用于作为 NT 服务运行的应用程序时。当 NT 启动时,应用程序也启动了。问题是,没有任务栏,没有系统托盘,直到有人登录前都没有地方放置图标。这个问题已经修复了。
Michael Dun 添加了对 Windows 2000 的支持——即那些酷炫的气球提示。
该类现在支持新的 Windows 2000 功能,以及 CE(包括从托盘弹出的菜单)。还添加了两个用于将应用程序最小化到系统托盘的新函数。
2000年9月21日 - Matthew Ellis 通过提供一个改进版的 GetTrayWndRect
函数来搜索系统托盘的位置,从而改进了最小化到托盘的功能。他还提供了一个函数 GetDoWndAnimation
,用于检查系统是否设置为显示窗口动画(用于最小化/最大化),如果没有,则不显示动画。
还有一个非 MFC 版本。
Håkan Trygg 对该类进行了如下更新:
菜单消息不再总是发送到主窗口(AfxMainWnd
),而是发送到另一个指定的窗口。这个窗口被称为“目标”窗口。如果没有指定窗口,则使用 AfxMainWnd
。
新的函数是:
BOOL SetTargetWnd(CWnd* pTargetWnd);
CWnd* GetTargetWnd() const;
另外:托盘图标的创建标志被保存下来,以便在需要重新创建图标时(例如设置更改、任务栏重建等),图标能够被正确创建。
2002年6月16日 添加了 ASSUME_IE5_OR_ABOVE
定义并修复了 VC 7.0 的编译错误。
2003年8月3日 - 添加了一些小的修复以及 CustomizeMenu
方法。感谢 Anton Treskunov 提供的这个方法和修复。
Håkan Trygg 还添加了用于持有和更改菜单的方法。
BOOL SetMenuText(UINT uiCmd, LPCTSTR szText); BOOL SetMenuText(UINT uiCmd, UINT uiID); BOOL CheckMenuItem(UINT uiCmd, BOOL bCheck); BOOL EnableMenuItem(UINT uiCmd, BOOL bEnable); BOOL DeleteMenu(UINT uiCmd);
更新后的类文件可以在这里下载。它们尚未合并到主类中,仅仅是因为我还没有时间测试——但我认为它们足够重要,不想延迟提供。