将 .NET 控件嵌入到 NotifyIcon 气泡提示






4.73/5 (28投票s)
如何将 .NET 控件嵌入到 NotifyIcon 气泡提示中
引言
有人问我是否可以将超链接添加到 NotifyIcon 气泡提示中。 使用 WinAPI 添加它并不太难,但产生了一个想法,即将其嵌入到 .NET 控件中。 这可能有很多用途。 同样,您可以添加超链接,使访问者转到指定的网站。 您可以将图像添加到工具提示等等。 因此,我使用了标准的 .NET NotifyIcon
并尝试修改它。 这是一个示例,展示了它的外观

但是在实现过程中出现了一些问题。 让我们回顾一下它们。
问题
第一个问题
NotifyIcon
是一个密封类,因此您不能从中继承并覆盖其 ShowBalloonTip
方法。 解决方案是为 NotifyIcon
创建一个包装器类,并实现我们自己的方法和属性。 其中一些可以通过重定向到 NotifyIcon
对象来实现,然后由我们的类公开。 这很简单,因为 NotifyIcon
不包含很多内容。 让我们创建两组属性
BalloonTipIcon
BalloonTipTitle
ContextMenu
ContextMenuStrip
Text
Icon
Visible
在此组中,我们将使用与 NotifyIcon
相同的属性。
BalloonTipText
由于我们将重新绘制工具提示的内容,因此不会有这样的属性,我们将用 InsidePanel
属性替换它,并传递一个 Panel
,该 Panel
将包含任何 .NET 控件。
另外,在我们的包装类中,我们应该定义 NotifyIcon
事件
NotifyClicked
NotifyDoubleClicked
NotifyMouseClicked
NotifyMouseUp
NotifyMouseDown
NotifyMouseMove
NotifyMouseDoubleClicked
实现 BalloonTipClicked
没有意义,因为工具提示将包含一个带有自己事件的面板。 将只有 BalloonTipClosed
和 BallonTipShown
事件。 我们将实现我们自己的 ShowBalloonTip
方法。
第二个问题
我们将需要 NotifyIcon
的 HWND
,但它没有公开它。 为了实现它,我们将使用反射来获取 NotifyIcon
的 private
字段“window
”
private IntPtr GetHandler(Object @object)
{
FieldInfo fieldInfo = @object.GetType().GetField("window",
BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
NativeWindow nativeWindow = (NativeWindow)fieldInfo.GetValue(@object);
if (nativeWindow.Handle == IntPtr.Zero)
return IntPtr.Zero;
return nativeWindow.Handle;
}
第三个问题
它是找到 Notify Icon 的屏幕矩形,以在正确的位置显示工具提示。 在这里 WINAPI
派上用场。 首先,通过使用 FindWindowEx
,我们将通过其“Shell_TrayWnd
”类名称找到窗口托盘
IntPtr hWndTray = WINAPI.FindWindowEx(IntPtr.Zero, IntPtr.Zero, "Shell_TrayWnd", 0);
if (hWndTray == IntPtr.Zero)
return false;
然后,通过使用 EnumChildWindows
,我们将找到“ToolbarWindow32
”类。
WINAPI.EnumChildWindowsCallback callback =
new WINAPI.EnumChildWindowsCallback(EnumChildWindowsFunc);
...
WINAPI.EnumChildWindows(hWndTray, callback, 0);
if (!mFound)
return false;
...
protected bool EnumChildWindowsFunc(IntPtr hwnd, IntPtr lParam)
{
StringBuilder sb = new StringBuilder(256);
WINAPI.GetClassName(hwnd, sb, sb.Capacity);
if (sb.ToString().StartsWith("ToolbarWindow32"))
{
mHWndNotify = hwnd;
mFound = true;
return false;
}
mFound = false;
return true;
}
最后,通过使用 MapWindowPoint
,我们将找到通知图标矩形。
最后步骤
此时,我们将创建一个本机窗口作为工具提示和气泡。 工具提示的内容将在其窗口过程中重新绘制。 因此,应保存旧的窗口过程,以便在我们的实现中使用以绘制原始工具提示。
//Create parameters for a new tooltip window
System.Windows.Forms.CreateParams moCreateParams =
new System.Windows.Forms.CreateParams();
// New window is a tooltip and a balloon
moCreateParams.ClassName = WINAPI.TOOLTIPS_CLASS;
moCreateParams.Style = WINAPI.WS_POPUP | WINAPI.TTS_NOPREFIX |
WINAPI.TTS_ALWAYSTIP | WINAPI.TTS_BALLOON;
moCreateParams.Parent = loNotifyIconHandle;
// Create the tooltip window
moNativeWindow.CreateHandle(moCreateParams);
//We save old window proc to be used later and replace it with our own
IntPtr loNativeProc = WINAPI.SetWindowLong(moNativeWindow.Handle,
WINAPI.GWL_WNDPROC, wpcallback);
if (loNativeProc == IntPtr.Zero)
return;
if (WINAPI.SetProp(moNativeWindow.Handle, "NATIVEPROC", loNativeProc) == 0)
return;
注意:窗口过程委托应定义为我们类的一个字段,因此它不能被垃圾回收。
在显示工具提示之前,我们必须调整它的大小,以便我们的 .NET 面板适合其客户端区域。 我没有找到如何直接执行此操作,唯一的解决方案是使用 GetTextExtentPoint32
获取 char
大小,并创建一个相应的 string
,以便工具提示具有所需的大小。
好的,现在是窗口过程的时间了。 首先,我们调用原始窗口过程,以便形成工具提示。 在处理 WM_PAINT
消息时,我们将获得工具提示的大小并清除工具提示的文本区域。 为了能够在工具提示的右上角关闭工具提示,我们将添加一个 .NET 按钮
// 16 pixs for the button control and 2 pixels from top
moCloseButton.Location = new System.Drawing.Point
(width - (rect.left + 16), rect.top - 2);
moCloseButton.AutoSize = false;
moCloseButton.Click += CloseButtonClick;
它将关闭工具提示
private void CloseButtonClick(object sender, EventArgs e)
{
CloseToolTip();
}
private void CloseToolTip()
{
WINAPI.TOOLINFO ti = new WINAPI.TOOLINFO();
ti.cbSize = Marshal.SizeOf(ti.GetType());
ti.hwnd = GetHandler(moNotifyIcon);
WINAPI.SendMessage(moNativeWindow.Handle, WINAPI.TTM_DELTOOL, 0, ref ti);
...
}
最后,带有任何 .NET 控件的面板位于 NotifyIcon
工具提示中
...
//Adding panel to the tooltip
moPanel.Location = new Point(rect.left, rect.top + 16);
WINAPI.SetParent(moPanel.Handle, hWnd);
...
已知问题
首先,我没有分析 SysTray
是否位于屏幕的顶部、左侧或右侧。 我只是假设它在底部。
如果您将鼠标指针设置到通知图标,然后将其沿着 SysTray
工具栏移动到另一个图标,则会发生另一个问题。 .NET 控件未添加到工具提示,并且它会随着用于调整其大小的 string
闪烁。
历史
- 2009 年 9 月 17 日:首次发布