程序的单例模式

对于一些程序有时需要限制实例个数为单例,如同一时刻,只能有一个实例存在。具体的实现方式主要有互斥锁Mutex和查询进程Process

一、 判断是否已创建对应的实例对象

1)、通过Mutex来判断是否为多实例对象

  1. 首先判断调用的线程是否拥有已初始化的互斥锁,如果true则表示已经存在对应的实例对象了,false表示当前还未创建对应实例对象。
  2. 如果判断true,即已经存在对应的实例对象,则现在正在创建的实例程序将被关闭。
using System.Windows;
using System.Threading;

namespace Simple_Test
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        Mutex myMutex;
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            bool isFirstInstance = false;
            myMutex = new Mutex(true, "MyInstanceApp",out isFirstInstance);
            if(!isFirstInstance)
            {
                // another instance is created,there is already an instance is running

                App.Current.Shutdown();
            }
        }
    }
}

2)、通过检索当前实例的进程数是否大于1来判断

若等于1,则表示只有一个实例对象,若大于1则表示有多个实例对象(包含当前正在创建的这个实例对象)

using System.Windows;
using System.Threading;
using System.Diagnostics;
using System.Linq;

namespace Simple_Test
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            Process process = Process.GetCurrentProcess();
            int count = Process.GetProcesses().Where(p => p.ProcessName == process.ProcessName).Count();
            if (count > 1)
                App.Current.Shutdown();
        }
    }
}

二、 判断是否已存在实例对象,若存在则将该窗口置于顶层

  1. 先判断对应的实例对象是否已经创建。
  2. 若已经创建,则将对应的实例窗口至于显示器顶层,以让User进行操作

1)、Mutex --> PostMessage --> ReceiveMessage

App.xaml.cs 文件中判断对应的实例是否已经存在

using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace SingleInstance
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        Mutex singleMutex;

        private void StartupApp()
        {
            singleMutex = new Mutex(true, "SingleInstance");
            //设置等待时间为0,以防止阻塞
            if (singleMutex.WaitOne(TimeSpan.Zero, true))
            {
                //the first app instance has been created.
                singleMutex.ReleaseMutex();
            }
            else
            {
                // When another app instance is ready to create, posting a message to WndProc.
                WrapWin32Methods.PostMessage((IntPtr)WrapWin32Methods.HWND_BROADCAST,
                    WrapWin32Methods.WM_SHOWME,
                    IntPtr.Zero,
                    IntPtr.Zero);

                // Already application Window will show, and shutdown the current application.
                Application.Current.Shutdown();
            }

        }

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            StartupApp();     
        }

    }
}

WrapWin32Methods.cs 文件中封装一些Win32API

using System;
using System.Threading.Tasks;
using System.Windows.Interop;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Media;

//This class just wraps some Win32 methods that we're going to use
namespace SingleInstance
{
    internal class WrapWin32Methods
    {
        public const int HWND_BROADCAST = 0xffff;

        [DllImport("user32")]
        public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
        [DllImport("user32")]
        public static extern int RegisterWindowMessage(string message);

        public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");

        /// <summary>
        /// Method_1: Add an event handler that receives all window messages
        /// </summary>
        /// <param name="wind"></param>
        public void AddHookWndProc(Visual curVisual)
        {
            HwndSource hwndSource= PresentationSource.FromVisual(curVisual) as HwndSource;
            hwndSource.AddHook(WndProc);
        }

        /// <summary>
        /// Method_2: Add an event handler that receives all window messages
        /// </summary>
        /// <param name="curWindow"></param>
        public void AddHookWndProc(Window curWindow)
        {
            if(curWindow!=null)
            {
                IntPtr hwnd = new WindowInteropHelper(curWindow).Handle;
                HwndSource.FromHwnd(hwnd).AddHook(WndProc);
            }
        }


        /// <summary>
        /// Used to handle received windows message.
        /// </summary>
        /// <param name="hwnd"></param>
        /// <param name="msg"></param>
        /// <param name="wParam"></param>
        /// <param name="lParam"></param>
        /// <param name="handled"></param>
        /// <returns></returns>
        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            // Handle the received message.
            if(msg == WM_SHOWME)
            {
                //MessageBox.Show("Already exist.");
                if (Application.Current.MainWindow.WindowState == WindowState.Minimized)
                {
                    Application.Current.MainWindow.WindowState = WindowState.Normal;
                }
                Application.Current.MainWindow.Topmost = true;

                RestoreState();
            }


            return IntPtr.Zero;
        }


        /// <summary>
        /// Restore the window TopMost to false, otherwise the window will 
        /// will always on the top of other window.
        /// </summary>
        private async void RestoreState()
        {
            await Task.Delay(100);
            Application.Current.MainWindow.Topmost = false;
        }

    }
}


MainWindow.xaml.cs 文件中将封装的Win32 窗口过程函数添加到Hook上,以能抓到对应的消息,即 WM_SHOWME
【注意】AddHook 的调用必须要等到MainWindow完成初始化之后,否则 new WindowInteropHelper(curWindow).Handle 返回值将为0,最终将导致无法AddHook到对应的句柄上。可以将该函数的调用放在程序完全起来之后的某个时机点

using System;
using System.Windows;

namespace SingleInstance
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        
        public MainWindow()
        {
            InitializeComponent();
        }

        protected override void OnContentRendered(EventArgs e)
        {
            base.OnContentRendered(e);

            WrapWin32Methods wrapMethod = new WrapWin32Methods();

            Window curWindow = Application.Current.MainWindow;
            wrapMethod.AddHookWndProc(curWindow);
        }

        // OnSourceInitialized函数中添加 AddHook 也可以
        //protected override void OnSourceInitialized(EventArgs e)
        //{
        //    base.OnSourceInitialized(e);
        //}
    }
}

2)、通过该进程的方式

[DllImport("User32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);

[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);


// 首先获取该进程的名称,然后在构造函数或者其它地方调用该函数
private void ShowSpecifiedWindow(string processName)
{
    //将指定的窗口APP放在屏幕最上层
    Process[] getPro = Process.GetProcessesByName(processName);
    if (getPro.Count() > 0)
    {
        if (getPro[0].MainWindowHandle != IntPtr.Zero)
        {
            ShowWindow(getPro[0].MainWindowHandle, SW_SHOWNORMAL);
            SetForegroundWindow(getPro[0].MainWindowHandle);
        }
    }
}


参考资料:

  1. WPF Single Instance Best Practices
  2. What is the correct way to create a single-instance WPF application?
posted @ 2022-08-24 12:01  Jeffxue  阅读(72)  评论(0编辑  收藏  举报