对于一些程序有时需要限制实例个数为单例,如同一时刻,只能有一个实例存在。具体的实现方式主要有互斥锁Mutex
和查询进程Process
。
一、 判断是否已创建对应的实例对象
1)、通过Mutex
来判断是否为多实例对象
- 首先判断调用的线程是否拥有已初始化的互斥锁,如果true则表示已经存在对应的实例对象了,false表示当前还未创建对应实例对象。
- 如果判断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();
}
}
}
二、 判断是否已存在实例对象,若存在则将该窗口置于顶层
- 先判断对应的实例对象是否已经创建。
- 若已经创建,则将对应的实例窗口至于显示器顶层,以让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);
}
}
}
参考资料:
- WPF Single Instance Best Practices
- What is the correct way to create a single-instance WPF application?