WPF没有托盘图标,所以需要使用System.Windows.Forms命名空间中的NotifyIcon
1、NotifyIcon基本使用
Icon图标显示,一般为ico文件。
Text为鼠标悬浮提示。
BalloonTipTitle,BalloonTipText为气泡弹窗提示的标题及内容,ShowBalloonTip为显示气泡弹窗参数为时间(ms)。
ContextMenuStrip为右键菜单。
MouseDoubleClick为图标双击事件,
更多用法,可以参考MDSN的例子,链接为:
https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.forms.notifyicon
注意:
你会发现例子中使用的是System.Windows.Forms.ContextMenu , 而不是ContextMenuStrip。
那是因为在net 3.1以上已经弃用了System.Windows.Forms.ContextMenu。
链接:
https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.forms.contextmenu
更多基本使用请参考一下链接:
https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.forms.notifyicon
https://blog.csdn.net/shikong_/article/details/76684852
https://www.cnblogs.com/mq0036/p/18902717
好,基本使用看到这里就可以了。
2、对NotifyIcon进一步封装及配合其他框架如DependencyInjection的使用
2.1、封装
对NotifyIcon进一步封装如下:
using System.Drawing;
using System.Windows;
using NotifyIcon_托盘图标;
using NotifyIcon_托盘图标.Utils;
using Forms = System.Windows.Forms;
namespace NotifyIcon_托盘图标.Utils
{
public class AppNotifyIcon
{
private static readonly Lazy<AppNotifyIcon> instance = new Lazy<AppNotifyIcon>(() =>
new AppNotifyIcon()
);
private readonly Forms.NotifyIcon icon;
private static readonly Uri iconUri = new(
AppDomain.CurrentDomain.BaseDirectory + @"Resources\devil_ico.ico",
UriKind.RelativeOrAbsolute
);
public event Forms.MouseEventHandler? DoubleClickEvent;
public event EventHandler? ExitClick;
private AppNotifyIcon()
{
icon = new Forms.NotifyIcon();
InitialIcon(icon);
}
private void InitialIcon(Forms.NotifyIcon icon)
{
icon.Icon = new Icon(iconUri.LocalPath);
icon.Visible = true;
icon.Text = "NotifyIcon_悬浮提示ToolTip";
icon.BalloonTipTitle = "已启动";
icon.BalloonTipText = "运行中。。。";
//添加右键菜单
Forms.ContextMenuStrip cms = new Forms.ContextMenuStrip();
icon.ContextMenuStrip = cms;
Forms.ToolStripMenuItem exitMenuItem = new Forms.ToolStripMenuItem("退出");
exitMenuItem.Click += (sender, e) =>
{
ExitClick?.Invoke(sender, e);
};
cms.Items.Add(exitMenuItem);
//鼠标双击事件
icon.MouseDoubleClick += (sender, e) =>
{
DoubleClickEvent?.Invoke(sender, e);
};
icon.ShowBalloonTip(1000);
}
public void Close(ExitEventArgs e)
{
icon.Dispose();
}
public void ShowIconBalloon(string Title,string Text ,int timeout)
{
icon.BalloonTipTitle = Title;
icon.BalloonTipText = Text;
icon.ShowBalloonTip(timeout);
}
public static AppNotifyIcon Instance => instance.Value;
}
}
namespace Microsoft.Extensions.DependencyInjection
{
public static class AppNotifyIconExension
{
public static void AddAppNotifyIcon(this ServiceCollection services, App app)
{
services.AddTransient<AppNotifyIcon>((provider) => AppNotifyIcon.Instance);
app.Exit += (object sender, ExitEventArgs e) =>
{
AppNotifyIcon.Instance.Close(e);
};
}
public static void HideWinInIcon(this Window window)
{
window.Hide();
AppNotifyIcon.Instance.ShowIconBalloon("最小化到托盘","后台运行中。。。", 1000);
}
}
}
封装功能如下:
1,由于一个软件一般都是只有一个托盘图标所以封装成单例AppNotifyIcon,私有化构造函数,并使用Lazy来懒加载AppNotifyIcon。
2,将一些事件外放,交由外部调用者在事件发生后执行他们的逻辑。
//鼠标双击图标事件
public event Forms.MouseEventHandler? DoubleClickEvent;
//点击菜单退出事件
public event EventHandler? ExitClick;
当然你甚至可以将icon的菜单外放,让调用者自行调整右键菜单选项。
//Public让外部可调用调整右键菜单
Public ContextMenuStrip cms = new Forms.ContextMenuStrip();
...
//在某处关联上
icon.ContextMenuStrip = cms;
//
3,配合DependencyInjection使用,编写扩展方法,在App退出事件里Dispose掉NotifyIcon
(不释放NotifyIcon会造成“僵尸图标”:即程序退出后图标还在,只有当鼠标指向才会消失)
2.2、使用
1,在APP里注册进去
using Microsoft.Extensions.DependencyInjection;
using System.IO;
using System.Text.Json;
using System.Windows;
namespace NotifyIcon_托盘图标
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private ServiceCollection? services;
private IServiceProvider? provider;
protected override void OnStartup(StartupEventArgs e)
{
services = new ServiceCollection();
services.AddSingleton<MainWindow>();
services.AddTransient<MainVM>((provider) => {
try
{ //加载配置
using (FileStream fs = new FileStream(MainVM.configJsonFile, FileMode.Open))
{
using(StreamReader sr = new(fs))
{
var str = sr.ReadToEnd();
return JsonSerializer.Deserialize<MainVM>(str)??new MainVM();
}
}
}
catch (FileNotFoundException e)
{
return new MainVM();
}
});
services.AddAppNotifyIcon(this);
//services.AddSingleton<AppNotifyIcon>( privider => AppNotifyIcon.Instance );
provider = services.BuildServiceProvider();
App.Current.MainWindow = provider.GetService<MainWindow>();
App.Current.MainWindow?.Show();
base.OnStartup(e);
}
}
}
2,在Mainwindow里实现事件调用逻辑
using System.IO;
using System.Net.NetworkInformation;
using System.Text;
using System.Text.Json;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Extensions.DependencyInjection;
using NotifyIcon_托盘图标.Utils;
namespace NotifyIcon_托盘图标
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private IServiceProvider provider;
private readonly AppNotifyIcon icon;
private readonly MainVM vm;
//图标右键强制退出标志
private bool forceExit = false;
public MainWindow(IServiceProvider provider, MainVM vM, AppNotifyIcon icon)
{
this.provider = provider;
this.icon = icon;
this.vm = vM;
DataContext = this.vm;
//双击激活窗体
this.icon.DoubleClickEvent += (sender, e) =>
{
this.Show();
this.WindowState = WindowState.Minimized;
this.WindowState = WindowState.Normal;
this.Activate();
};
//退出事件
this.icon.ExitClick += (sender, e) =>
{
forceExit = true;
this.Close();
};
InitializeComponent();
}
private void WinClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
//右键强制退出
if (forceExit)
return;
//退出时询问
if (vm.AskIsMinInIconWhenClose)
{
var res = MessageBox.Show(
"是否关闭程序,否则最小化到托盘",
"关闭行为",
MessageBoxButton.YesNoCancel
);
if (res == MessageBoxResult.Yes)
return;
if (res == MessageBoxResult.No)
{
e.Cancel = true;
this.HideWinInIcon();
}
if (res == MessageBoxResult.Cancel)
e.Cancel = true;
}
else
{
//最小化到托盘
if (vm.IsMinimize)
{
e.Cancel = true;
this.HideWinInIcon();
}
else
return;
}
}
private void WinLoaded(object sender, RoutedEventArgs e)
{
if (vm != null)
{
//程序启动最小化到托盘
if (vm.MinInIconWhenStart)
{
this.HideWinInIcon();
}
}
}
private void WinClosed(object sender, EventArgs e)
{
//保存配置
using (FileStream fs = new FileStream(MainVM.configJsonFile, FileMode.Create))
{
using (StreamWriter sw = new StreamWriter(fs))
{
sw.AutoFlush = true;
sw.Write(JsonSerializer.Serialize(vm));
}
}
}
}
}
3,MainVm保存配置
using CommunityToolkit.Mvvm.ComponentModel;
namespace NotifyIcon_托盘图标
{
public partial class MainVM:ObservableObject
{
public const string configJsonFile = "CloseConfig.json";
[ObservableProperty]
private bool minInIconWhenStart = false;
[ObservableProperty]
private bool askIsMinInIconWhenClose = true;
[ObservableProperty]
private bool isMinimize = true;
}
}
最后实现效果: