WPF 托盘图标

51 阅读3分钟

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;
    }
}

最后实现效果: