WPF - 单例程序和系统托盘

Tim 25 2024-10-22

记录一下工作中遇到的场景。单例运行程序同时还要将程序隐藏到系统托盘,再次启动程序时如果已存在需要调出隐藏的程序

Mutex 互斥锁

程序启动入口添加互斥锁判断:

public  System.Threading.Mutex mutex;
public static bool AppSingletonMark = false;

protected override void OnStartup(StartupEventArgs e)
{
     bool isNew;
     mutex = new Mutex(true, "Singleton Instance Sample", out isNew);
     if (!isNew)//isNew为false代表已经存在,所以需要关闭正在打开的程序
     {
        //这里可以添加唤起已启动的程序
        ... ...

        AppSingletonMark = true;//标识是否需要隐藏程序
        Shutdown();
     }  
 }

NotifyIcon 系统托盘

在主窗体加载的同时创建系统托盘:

private System.Windows.Forms.NotifyIcon notifyIcon;

private void InitialTray()
{
    this.notifyIcon = new System.Windows.Forms.NotifyIcon();
    this.notifyIcon.BalloonTipText = "提示文本";
    this.notifyIcon.ShowBalloonTip(8000);
    this.notifyIcon.Text = "Wits监听中...";
    //this.notifyIcon.Icon = new System.Drawing.Icon("../../activity_monitor.ico");
    //以上一句替换成下面内容,意思就是读取程序图标,来作为托盘图标
    this.notifyIcon.Icon = System.Drawing.Icon.ExtractAssociatedIcon(System.Windows.Forms.Application.ExecutablePath);
    this.notifyIcon.Visible = true;

    //打开菜单项
    System.Windows.Forms.MenuItem show = new System.Windows.Forms.MenuItem("Show");
    show.Click += new EventHandler(Show);
    //退出菜单项
    System.Windows.Forms.MenuItem exit = new System.Windows.Forms.MenuItem("Exit");
    exit.Click += new EventHandler(Close);
    //将菜单关联到托盘控件
    System.Windows.Forms.MenuItem[] childen = new System.Windows.Forms.MenuItem[] { show, exit };
    this.notifyIcon.ContextMenu = new System.Windows.Forms.ContextMenu(childen);

    //单机系统托盘图标,显示程序
    this.notifyIcon.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler((o, e) =>
    {
        if (e.Button == MouseButtons.Left) this.Show(o, e);
    });
}

 /// <summary>
 /// 显示主窗体
 /// </summary>
 /// <param name="sender"></param>
 /// <param name="e"></param>
 private void Show(object sender, EventArgs e)
 {
     this.Visibility = System.Windows.Visibility.Visible;
     this.ShowInTaskbar = true;
     this.Activate();
     //this.notifyIcon.Visible = false;
 }
private void Close(object sender, EventArgs e)
{
   Environment.Exit(0);
   System.Windows.Application.Current.Shutdown();
}

Win32 唤起隐藏程序

需要注意的是对于隐藏的程序还要使用 SendMessage 否则程序将会黑屏

/// <summary>
/// 正常显示窗口值
/// </summary>
private const int WS_SHOWNORMAL = 1;

/// <summary>
/// 当隐藏或显示窗口是发送此消息给这个窗口 
/// </summary>
private const int WM_SHOWWINDOW =0x18;

/// <summary>
/// 设置由不同线程产生的窗口的显示状态
/// </summary>
/// <param name="hWnd">窗口句柄</param>
/// <param name="cmdShow">窗口如何显示</param>
/// <returns></returns>
[DllImport("User32.dll")]
private static extern bool ShowWindowAsync(IntPtr hWnd, int cmdShow);

/// <summary>
/// 把创建给定窗口的线程放到前台并激活该窗口                                 
/// </summary>
/// <param name="hWnd">窗口句柄</param>
/// <returns></returns>
[DllImport("User32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);

/// <summary>
/// 查找窗口句柄
/// </summary>
/// <param name="lpClassName">窗口类名</param>
/// <param name="lpWindowName">窗口文本</param>
/// <returns></returns>
[DllImport("User32.dll", EntryPoint = "FindWindow")]
private extern static IntPtr FindWindow(string lpClassName, string lpWindowName);


[DllImport("User32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

public void HandleRunningInstance(string windowsTitle)
{
    //通过窗体的title查找句柄
    var windowHandle = FindWindow(null, windowsTitle); 
  
    //发送显示消息到指定窗体
    SendMessage(windowHandle, WM_SHOWWINDOW, (IntPtr)1,(IntPtr)3);

    //确保窗口没有被最小化或最大化 
    ShowWindowAsync(windowHandle, WS_SHOWNORMAL);
    
    //设置为foreground window 
    SetForegroundWindow(windowHandle);
}