WPF 友好的 Shell_NotifyIcon 包装类 - 第二部分:最小化到系统托盘





5.00/5 (1投票)
本系列文章探讨了一个新的 WPF 友好的 Shell_NotifyIcon 包装器类。
引言
本系列文章介绍了一个新的 Shell_NotifyIcon
包装器类,这是一个出了名的难以缠斗的 Win32 API。尽管 WinForms 提供了一个 NotifyIcon
类,但其 API 仅比 Win32 API 稍好一些,文档也同样如此。我想独立于 WinForms,所以我创建了这个包装器类。
本文(第二部分:MinimizeToSystemTray
)扩展了包装器库公开的 API,并提供了一些简单的示例来展示扩展的功能。
背景
最小化到系统托盘
本系列的第 1 部分 侧重于 NotifyIconWrapper
及其 API。本部分将探讨 NotifyIconWrapper
的一个应用。其思路是采用一个具有顶级窗口的应用程序,并尽可能少地修改它,以便将该顶级窗口最小化到通知图标而不是任务栏图标。这听起来并不是一个巨大的挑战,但它比看起来要难。例如,在保持窗口为顶级窗口的同时,不容易抑制任务栏图标。我选择隐藏顶级窗口,而不是尝试以其他方式改变其外观。通知图标与顶级窗口的隐藏和显示同步,但它并不是 NotifyIcon
所附加的窗口。在切换应用程序时(例如,通过使用 Alt-Tab),您可能仍然会在动画中注意到隐藏的顶级窗口。微软在不同上下文中显示或隐藏窗口方面一直不一致。这是我目前为止找到的最佳折衷方案。
我整理了一个示例程序来演示完成此任务的能力。我很快意识到,有很多代码并非特定于应用程序的需求。我试图将这些公共代码分离出来,以便用户能够更轻松地将此功能添加到新应用程序或现有应用程序中。结果是一个新的 API。
Using the Code
这是 MinimizeToSystemTrayDemo1
的主窗口,滚动到文档顶部
这是 MinimizeToSystemTrayDemo1
的主窗口,滚动到文档底部
这是 MinimizeToSystemTrayDemo2
的主窗口
这是 MinimizeToSystemTrayDemo2
的目标窗口
让我们看看 MinimizeToSystemTrayDemo1
的主窗口构造函数
public MainWindow()
{
InitializeComponent();
_ = new MinimizeToSystemTray(this);
// Because one of the parameters that was defaulted in the above call is of type
// System.Drawing.Icon, a reference to System.Drawing had to be added to this
// project.
}
您可以看到,只需要额外一行代码即可实现此功能。请注意,除了对 MinimizeToSystemTrayLibrary
的引用外,还需要对 System.Drawing
的引用,如上面的注释中所述。
运行 MinimizeToSystemTrayDemo1
时,主屏幕上显示的文本解释了开箱即用的实现所提供的功能。
相比之下,MinimizeToSystemTrayDemo2
使用了 MinimizeToSystemTray
构造函数的可选参数,为 NotifyIcon
提供了初始自定义图标和工具提示。
/// <summary>
/// This is the public constructor for the TargetWindow class.
/// </summary>
/// <param name="iconSource">
/// This parameter provides the initial icons for the window and its NotifyIcon.
/// </param>
/// <param name="notifyIconToolTip">
/// This parameter provides the initial tool tip text for this window's NotifyIcon.
/// </param>
public TargetWindow(IconSource iconSource, string notifyIconToolTip)
{
InitializeComponent();
IconSource = iconSource;
NotifyIconToolTip = notifyIconToolTip;
_minimizeToSystemTray =
new MinimizeToSystemTray(this, IconSource.Icon, NotifyIconToolTip);
}
MinimizeToSystemTrayDemo2
引入了一个名为 IconSource
的类,用于设置窗口图标并将其与通知图标进行协调。API 需要不同格式的图标。IconSource
初始化自身两次,从单个资源加载,以便框架处理转换。注意 BeginInit()
和 EndInit()
的重要调用。您不经常看到对这些例程的调用。在此处使用它们是因为 UriSource
通常在 InitializeComponent()
中调用,它提供对 BeginInit()
和 EndInit()
的调用,以协调 XAML 代码的整体解析。
/// <summary>
/// This is the public constructor for the <c>IconSource</c> class. It uses
/// <paramref name="key"/> to construct a Uri for the .ico resource file containing
/// the matching icon, then loads the icon for the notify icon and constructs the icon for
/// the window, both based on this uri.
/// </summary>
/// <param name="key">
/// This is the key that provides the variable portion of the .ico file name.
/// </param>
public IconSource(string key)
{
Uri uri = new Uri($"pack://application:,,,/{key}.ico", UriKind.Absolute);
System.IO.Stream s = Application.GetResourceStream(uri)?.Stream;
if (s == null) throw new InvalidOperationException();
Icon = new Icon(s);
BitmapImage img = new BitmapImage();
img.BeginInit();
img.UriSource = uri;
img.EndInit();
Source = img;
}
MinimizeToSystemTrayDemo2
使用单选按钮的 Tag
属性来按颜色选择图标,并选择几种工具提示文本中的一种。您可以在通知图标显示或隐藏时更改这些设置。结果将立即显示,或者在下次显示通知图标时显示。以这种方式使用 Tag
属性允许单个事件处理程序处理每组单选按钮。
MinimizeToSystemTrayDemo2
中有一个 private
方法,负责在窗口状态、通知图标状态之间进行同步。它在初始时、单选按钮状态更改时以及窗口最小化时被调用。
/// <summary>
/// Synchronize the notify icon with the window state of the client.
/// </summary>
private void SynchronizeState()
{
if (_client.WindowState == WindowState.Minimized)
{
_client.ShowInTaskbar = false;
_client.Hide();
_notifyIconWrapper.Update();
}
}
MinimizeToSystemTrayDemo2
中还有一个 private
方法,当发生适当的 NotifyIconWrapper
事件时,会调用该方法来将窗口恢复到其先前状态。请注意此处对 Delete()
的调用。这会导致 NotifyIconWrapper
通过 Win32 API 删除通知图标。NotifyIconWrapper
将在需要时创建一个新的通知图标。
/// <summary>
/// Restore the state of the window before it was minimized.
/// </summary>
private void RestoreClientWindowState()
{
// If the client was created in a minimized window state or, more specifically,
// if our constructor was called while our client was in a minimized window
// state, there will be no notify icon to delete the first time we get here.
// This is not a problem, however, because the notify icon source protects
// itself against this case.
_notifyIconWrapper.Delete();
_client.ShowInTaskbar = true;
_client.Show();
_client.WindowState = WindowState.Normal;
}
MinimizeToSystemTray 类
public class MinimizeToSystemTray : IDisposable
命名空间:MinimizeToSystemTrayLibrary
程序集:MinimizeToSystemTrayLibrary.dll
备注
此类使用 NotifyIconWrapper
来提供最小化到系统托盘的功能。
构造函数
最小化到系统托盘
public MinimizeToSystemTray(int callbackMessage = WindowMessages.User)
这是 MinimizeToSystemTray
类的构造函数。
client
是一个将要被服务的顶级窗口。icon
是一个可选的System.Drawing.Icon
类的图标,其中包含多个图标图像,可以从中提取一个合适的较小图标。tooltip
是一个可选的string
,用于通知图标的工具提示。
属性
图标
public System.Drawing.Icon Icon { get; set; }
获取或设置要显示为经典通知图标的图标。
提示
public String Tip { get; set; }
获取或设置在鼠标悬停在通知图标上时显示的工具提示文本。string
限制为 63 个字符。
方法
处置
public void(Dispose)
实现 IDisposible
模式。
关注点
每个可执行项目都提供了基本异常处理。在每种情况下,您都会在名为 program.cs 的文件中找到它。该文件还包含项目的入口点 (Main
)。我没有使用默认的启动代码,因为我认为它不适合我的目的,特别是,它没有提供一个统一的异常处理点。
本系列的下一篇文章 将讨论一个基于通知图标的广告拦截应用程序的前端。
历史
- 2020 年 12 月 1 日:初始版本