在Windows应用程序开发中,将顶级窗口最小化到系统托盘而不是任务栏是一个常见的需求。然而,实现这一功能并不像听起来那么简单。本文将介绍如何通过一个自定义的包装类,实现这一功能。
为了解决这个问题,选择了隐藏顶级窗口的方法,而不是尝试以其他方式改变其外观。系统托盘图标与顶级窗口的隐藏和显示同步,但它并不是NotifyIcon
所附加的窗口。在切换应用程序时,例如使用Alt-Tab
,仍然可能会注意到隐藏的顶级窗口在动画中出现。微软在不同上下文中显示或隐藏窗口的一致性方面做得并不好。这是目前为止能想到的最好的折中方案。
下面是一个示例程序,展示了如何实现这一功能。很快意识到,有很多代码并不特定于应用程序的需求。试图将这些通用代码提取出来,以便为想要在新应用程序或现有应用程序中添加此功能的用户,提供更多的即插即用体验。这就是新的API的来源。
以下是MinimizeToSystemTrayDemo1
主窗口构造函数的代码:
public MainWindow() {
InitializeComponent();
_ = new MinimizeToSystemTray(this);
// 因为上面的调用中有一个参数默认类型是System.Drawing.Icon,
// 所以必须向这个项目添加对System.Drawing的引用。
}
可以看到,实际上只需要添加一行代码就可以实现这个功能。需要注意的是,除了对MinimizeToSystemTrayLibrary
的引用外,还需要添加对System.Drawing
的引用,如上文注释所解释。
当运行MinimizeToSystemTrayDemo1
时,主屏幕上显示的文本解释了这个开箱即用的实现提供了什么。
相比之下,MinimizeToSystemTrayDemo2
使用MinimizeToSystemTray
构造函数的可选参数,为NotifyIcon
提供了初始自定义图标和工具提示:
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代码的整体解析:
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 void SynchronizeState() {
if (_client.WindowState == WindowState.Minimized) {
_client.ShowInTaskbar = false;
_client.Hide();
_notifyIconWrapper.Update();
}
}
在MinimizeToSystemTrayDemo2
中还有一个私有方法,当适当的NotifyIconWrapper
事件发生时,被调用来恢复窗口的先前状态。注意这里的Delete()
调用。这导致NotifyIconWrapper
通过Win32 API删除系统托盘图标。当需要时,NotifyIconWrapper
将创建一个新的系统托盘图标。
private void RestoreClientWindowState() {
// 如果客户端在最小化窗口状态下创建,或者更具体地说,
// 如果构造函数在客户端处于最小化窗口状态时被调用,
// 那么第一次到达这里时将没有系统托盘图标需要删除。
// 然而,这不是问题,因为系统托盘图标源保护了自己不受这种情况的影响。
_notifyIconWrapper.Delete();
_client.ShowInTaskbar = true;
_client.Show();
_client.WindowState = WindowState.Normal;
}
MinimizeToSystemTray
类使用NotifyIconWrapper
提供最小化到系统托盘的功能。
构造函数:
public MinimizeToSystemTray(int callbackMessage = WindowMessages.User)
这是MinimizeToSystemTray
类的构造函数。
属性:
public System.Drawing.Icon Icon {
get;
set;
}
// 获取或设置要显示为经典通知图标的图标。
public String Tip {
get;
set;
}
// 获取或设置当悬停在通知图标上时显示的工具提示文本。字符串限制为63个字符。
方法:
public void Dispose()
// 实现IDisposible模式。
感兴趣的点:
每个可执行项目都提供了基本的异常处理。在每种情况下,都会在名为program.cs
的文件中找到它。这个文件还包含项目的入口点(Main
)。没有使用默认的启动代码,因为觉得它不适合目的,特别是它没有提供一个处理异常的单一点。
本系列的下一篇文章将处理基于系统托盘图标的广告拦截应用程序的前端。