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

一个简单易用的托盘图标

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.39/5 (14投票s)

2010年11月15日

CPOL

5分钟阅读

viewsIcon

202496

downloadIcon

2571

一个可重用的 C++ 托盘图标类。

引言

本文将介绍一个可重用的 C++ 托盘图标类,它具有简单易用的接口。它只是对托盘图标相关的 WinAPI 的一个封装;它可以用于纯 Win32 控制台/窗口应用程序,包括基于 MFC 的应用程序。

背景

在像 Delphi 和 .NET 这样的基于组件的系统中,很容易找到一个托盘图标组件,你只需将其添加到窗口上,设置几个属性,一切就绪。在 C++ 中,我基于我一个 Delphi 4 项目中的组件编写了自己的版本。编写一个简单的托盘图标并不是什么难事,但其中存在一些陷阱,更不用说其不面向对象的、不友好的 API 接口了。

编写托盘图标的陷阱

使用纯 Win32 API 编写托盘图标基本上意味着将一个唯一的托盘图标 ID、一个图标和一个窗口句柄 (HWND) 组合在一起。您通过填写一个正确的 NOTIFYICONDATA 结构体,然后调用 Shell_NotifyIcon() 函数来执行所需的操作,并传递您的 NOTIFYICONDATA 结构体。第一件令人不快的事情是我们确实需要一个 HWND,这通常使得编写面向对象的、可重用的代码变得困难。这个 HWND 会接收发生在我们的小型托盘图标上的事件,如鼠标移动、左键单击、右键单击等……游戏中的另一部分是正确地填写 NOTIFYICONDATA 结构体,这可能会让初学者头疼。例如,在 VC++ 6 的旧 SDK 中,这是填写我们 NOTIFYICONDATA 结构体中 cbSize 成员的唯一有效方法:

NOTIFYICONDATA data;
data.cbSize = sizeof(data);

在较新的 SDK 中,该结构体的大小是变化的,而不是写入 sizeof(NOTIFYICONDATA),您必须使用 NOTIFYICONDATA_V1_SIZE,或 NOTIFYICONDATA_V2_SIZE,或者其他取决于您要使用的托盘图标函数所需的 SDK/Windows 版本的值。这是一个非常常见的错误。如果您在这里或别处出错,那么什么都不会发生,您将浪费时间在代码中寻找问题。

我们通常期望托盘图标具备的基本功能

  • 图标属性。
  • 名称属性。在我的类中,Name 属性是托盘图标工具提示的文本。
  • 可见属性。通过更改此属性,您可以随时显示/隐藏您的图标。
  • 显示气球提示以通知用户重要事项。(此功能仅在 Win2K+ 上可用,并且检测用户在气球提示上的点击(NIN_BALLOONUSERCLICK)仅在 WinXP+ 上可用。)
  • 我们希望能够使用我们的事件处理程序捕获与我们的托盘图标相关的鼠标移动和鼠标单击事件。例如,您可能希望捕获左键单击以将主窗口带到前台,并且跟踪右键单击时的弹出菜单也是用户期望的标准。

使用代码

您只需创建类的实例并设置其属性。要捕获鼠标事件,您还必须为图标设置一个监听器函数或对象,或者您可以从 CTrayIcon 派生自己的托盘图标类,通过重写 OnMessage() 方法来代替设置监听器。

好的,现在让我们讨论一下这个类是如何提供我们在上一节中列出的基本功能的!

首先,您需要创建一个 CTrayIcon 实例并设置其属性

// the constructor sets the name property
// to "example_icon", the visible property to true,
// and the icon property to LoadIcon(NULL, IDI_APPLICATION).
CTrayIcon tray_icon("example_icon", true, LoadIcon(NULL, IDI_APPLICATION));

上面的代码等同于以下内容

CTrayIcon tray_icon;
tray_icon.SetName("example_icon");
tray_icon.SetIcon(LoadIcon(NULL, IDI_APPLICATION));
tray_icon.SetVisible(true); // the icon is invisible by default

您可以随时更改属性。例如,您可以通过定期调用 SetIcon() 并传入您的图标,显示 2D 动画的帧,来创建一个动画图标。

在 Win2K 及更高版本中,您可以调用 ShowBalloonTooltip() 方法向用户显示带有消息的气球提示。如果您的应用程序在后台运行,并且您需要告诉用户某事,那么这是一个 MessageBox() 的良好替代方案;例如

tray_icon.ShowBalloonTooltip("GOOD NEWS", "Downloading xxx.dat has finished.", eTI_Info);

气球提示会在一段时间后(通常在 10 到 30 秒内)自动消失。其显示时间取决于多种因素,包括您的操作系统、注册表设置等。如果您想创建一个外观相同且行为更可控的提示,那么您需要寻找一个自定义的气球提示控件。

现在我们已经有了可见的托盘图标,可以设置它的工具提示、图标和可见性;唯一的问题是我们无法捕获鼠标事件。我们有三种选择来解决这个问题

// Option #1: sending events to a static method
static void TrayIcon_OnMessage(CTrayIcon* pTrayIcon, UINT uMsg)
{
  // your code here
}
...
tray_icon.SetListener(TrayIcon_OnMessage);

// Usage #2: sending events to our object
// implementing the ITrayIconListener interface
class MyClass : public ITrayIconListener
{
public:
  // override ITrayIconListener methods here
};
MyClass c;
tray_icon.SetListener(&c);

// Usage #3: deriving our own trayicon class and overriding OnMessage()
class CMyTrayIcon : public CTrayIcon
{
protected:
  virtual void OnMessage(UINT uMsg)
  {
    // your code here
  }
};
CMyTrayIcon tray_icon;

为了简单起见,我将使用一个静态方法作为监听器

static void TrayIcon_OnMessage(CTrayIcon* pTrayIcon, UINT uMsg)
{
  switch (uMsg)
  {
  case WM_LBUTTONUP:
    // here your bring your window to the foreground,
    // and maybe you hide your trayicon
    break;
  case WM_RBUTTONUP:
    // here your call GetCursorPos() and track a popupmenu at that position
    break;
  }
}
tray_icon.SetListener(TrayIcon_OnMessage);

请注意,我监听的是 WM_LBUTTONUP 消息而不是 WM_LBUTTONDOWN!!!这很重要!第一个原因是我想有一个 OnClick() 事件处理程序,而单击事件是鼠标按下和鼠标释放事件的组合。另一种不适合使用鼠标按下事件而不是鼠标释放事件的场景是,例如,当您响应事件而隐藏图标时。在这种情况下,您处理鼠标按下事件,但在隐藏托盘图标后,鼠标释放事件将由另一个托盘图标或任务栏接收,这通常会导致在用户不希望的情况下弹出其他应用程序的弹出菜单。一些应用程序会在主窗口可见时隐藏托盘图标,如果用户可以通过单击/双击托盘图标来恢复主窗口,则必须避免上述问题。

可下载的 zip 文件包含托盘图标源代码和一个极简示例程序,该程序向您展示如何将主窗口最小化到托盘。它还演示了右键单击时使用简单的弹出菜单。

历史

  • 2010 年 11 月 15 日:初始版本。
© . All rights reserved.