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

将图标添加到系统托盘

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (130投票s)

1999年11月30日

CPOL

10分钟阅读

viewsIcon

2974747

downloadIcon

44465

用于将图标添加到系统托盘的类。

Win9x sample winCE sample win2000 sample
在 Windows 9x, Windows CE 和 Windows 2000 中的示例图片

引言
然后,当你开始迭代 2(这是构建迭代的开始)时,你可能想要复制测试用例并将它们重新分类到迭代 2。这还允许对测试用例进行粒度跟踪,并允许你说某个测试用例在一个迭代中是准备好的,但在另一个迭代中不是。同样,如何做到这一点取决于你以及你希望如何报告。 “场景”部分提供了更多细节。
操作
图标
将应用程序最小化到系统托盘
默认消息处理
使用示例
关于 TrackPopupMenu 的注意事项
历史
最新

引言

CSystemTray 综合了来自 MSJ 的“Webster”应用程序、在线文档、其他实现(如 PJ Naughter 的 "CTrayNotifyIcon" (http://indigo.ie/~pjn/ntray.html))以及许多其他开发者的贡献。

这个类是对 Windows 系统托盘相关功能的轻量级封装。它向系统托盘添加一个图标,并附带指定的工具提示文本和回调通知值,该值会发送回父窗口。

旧方法

通过 Windows API 使用托盘图标的基本步骤是:

  1. 加载 NOTIFYICONDATA 结构体
  2. 调用 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,那么托盘图标将处理自己的托盘通知消息——但也会尝试处理从图标上下文菜单发送的菜单命令。要解决这个问题,您需要:

  1. 将托盘图标的父窗口设置为一个能够处理所有托盘图标通知的窗口,或者
  2. 将父窗口设置为 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 类中重写 OnDestroyOnShowWindow 函数,并添加以下代码行:

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

更新后的类文件可以在这里下载。它们尚未合并到主类中,仅仅是因为我还没有时间测试——但我认为它们足够重要,不想延迟提供。

© . All rights reserved.