使用 Win7 和 Vista 功能的 .NET WinForms 托盘图标实现





5.00/5 (2投票s)
使用 Win7 和 Vista 功能的 .NET WinForms 托盘图标实现:GUID 标识,大尺寸自定义图标,自定义 UI 替代提示等。
引言
不久前,我注意到 Windows 7 中网络连接托盘图标提示的奇怪行为。我知道 WinAPI 将托盘图标提示限制在 64 个字符(包括 `null` 终止符)以内,但这个提示是多行的,比 64 个字符的文本长得多,而且一些文本是*斜体*的!(在 Win8 中斜体消失了,只有一个长多行提示)所以,似乎 Win7 对托盘图标提示的了解比我知道的以及 WinForms 知道的要多得多。经过在 stackoverflow 和 MSDN 上的一些研究,我发现自 Windows Vista 起,托盘图标的 WinAPI 得到了扩展。但是,没有相关的文档和/或 .NET 包装器来支持新功能(只有 MSDN 的 `enum`/`struct` 成员描述)。甚至 WinAPI Code Pack 也忽略了它们。
我发现注意到的奇怪提示并不是扩展的托盘图标提示。新 API 允许应用程序使用其完全定义的 UI。而这并非唯一的新机遇。
因此,我决定创建自己的实现。对于任何使用 C#/WinForms 构建带托盘图标的应用程序的人来说,这可能都很有用。
背景
如 MSDN 所述,新 API 提供了以下新功能:
- 基于 GUID 的图标标识(取代旧式的 `HWND` 和图标索引)。
- 通知气泡中使用大尺寸(32x32 像素而非 16x16 像素)系统图标。
- 通知气泡中使用自定义图标。
- 使用应用程序定义的 UI,而不是标准 64 个字符限制的提示。
我研究了内置 `NotifyIcon` 组件的反编译源代码,发现它完全无法改进。首先,它大量使用了 WinForms 的内部方法。我可以通过反射调用它们,但是需要编写的代码太多,运行时消耗的资源也会太多。特别是,其中 70% 只是 WinAPI P/Invoke 包装器。
根据 WinAPI 的设计,不可能没有窗口来处理其消息而存在托盘图标。`NotifyIcon` 会为此目的创建自己的不可见窗口,并包含许多技巧,以使来自另一个窗口(用户代码窗口)的上下文菜单能够与其一起工作。这是第二大也是主要困难。
所以,如果我无法使用部分现有且可工作的代码(并且如果这段代码包含太多“变通方法”),我的实现将几乎是全新的。另外,我不喜欢使用自己的窗口的解决方案(为了处理托盘图标消息而拥有一个新窗口,而我总有一个自己的窗口——至少是应用程序的主窗体?..)。处理窗口消息需要访问窗口的 `WndProc`,并且我没有找到自动执行此操作的解决方案,因此我的组件不会像标准控件那样易于集成……但我认为这是值得的。
在一般情况下,使用扩展的托盘图标工具提示需要你自己编写该工具提示。如果不需要什么特殊的东西,那将只是一个长多行(但根据新的托盘图标 API 来说是自定义的)工具提示。内置的 WinForms `ToolTip` 控件主要用于显示控件的工具提示。它有一个定时器来显示和隐藏,并且与其他控件紧密关联。因此,新的提示窗口实现也随之诞生。有人可能会说我是在发明轮子。也许有人是对的。但我不认为我们可以强迫内置的 `ToolTip` 在任何地方显示类似这样的东西
Using the Code
要在你的代码中使用 `TrayIcon`,你必须执行以下操作:
- 将 *TrayIcon.cs* 及其依赖项添加到你的项目中
- 重新生成它,以便组件出现在 Forms Designer 工具箱中
- 将其放置在窗体上
- 将组件的 `OwnerForm` 属性设置为 `this`(包含的窗体,你可以在设计模式的 `PropertyGrid` 中进行设置),并根据需要设置其他属性(`Icon`、`HintText`、`GUID` 等)。将 `Enabled` 属性设置为 `true` 以启用托盘图标。
- 在窗体的代码中,重写 `WndProc` 并在其中调用 `TrayIcon` 的 `WndProc`。大致如下:
protected override void WndProc(ref Message m)
{
if (myTrayIcon.WndProc(ref m))
return;
base.WndProc(ref m);
}
`TrayIcon` 只会捕获托盘相关的消息,并将其他所有消息传递出去。
完成所有这些之后,你就可以享受带有新通知的新托盘图标了 :) 就像这样
几乎所有新功能都可以从 `TrayIcon` 的属性和方法中访问。
扩展工具提示不是托盘图标 API 的一部分,但此演示包含通用实现。要使用它,请将 `ShowDefaultTips` 设置为 `false`,并将 `LongHintText` 设置为你所需的长(且多行)文本。演示在 Windows XP 中以兼容模式工作,因此建议也将 `HintText` 设置为你提示的某个简短版本。如果 `ShowDefaultTips` 启用了,它也会显示。
你可以完全覆盖工具提示的大小和外观。要做到这一点,你必须为 `TooltipMeasure` 和 `TooltipPaint` 事件设置自己的处理程序,并在那里进行所有测量和渲染。此演示也这样做了,以便在工具提示中显示图标。要显示完全不同的 UI 来代替提示,请处理 `TooltipShown` 和 `TooltipClosed` 事件来显示和关闭你的 UI。
由于有一个自定义的托盘图标工具提示,我还完成了其他用途的实现。这不是标准 `ToolTip` 组件的替代品,但它也很有用。 `CustomHint` 组件使用自定义测量和渲染的相同原理。由于它是完全自定义的,它与控件没有任何联系,因此应该手动显示它。就像这样:
private void lblCustomHintTest_MouseLeave(object sender, EventArgs e)
{
customHint.Hide();
}
private void lblCustomHintTest_MouseEnter(object sender, EventArgs e)
{
customHint.Show(PointToScreen(new Point(lblCustomHintTest.Left,
lblCustomHintTest.Top + lblCustomHintTest.Height)));
}
在重写了它的渲染之后,你可以在窗体中使用像这样的奇怪东西:
历史
- 2014 年 10 月 22 日 - 发现问题并进行了大部分可行的解决方案
- 2014 年 10 月 23 日 - 集成到我的一个项目中并进行了测试;创建了一个演示项目并在此上传
- 2014 年 10 月 29 日 - 使用自定义提示和自定义托盘提示实现的更新