C#窗口实现单例模式的方法,对于一个软件如果第二次打开程序,就把已经启动的那个进程的窗口放到最前端显示
1 . 线程互斥量 - Mutex
System.Threading.Mutex
Mutex 是进程间同步的同步基元
所以我们可以通过在主线程中构建互斥量来判断一个主进程是否启动多次
1 2 3 4 5 6 7 8 9 10 11
| protected override void OnStartup (StartupEventArgs e) { bool _isRuning = false; mutex = new Mutex (true, Assembly.GetExecutingAssembly().FullName, out _isRuning); if (!_isRuning) { MessageBox.Show ("程序已经在运行,请勿重复启动!"); Environment.Exit (0); } base.OnStartup (e); }
|
上面的代码是 WPF 项目,我们是重写App 级视图类的OnStartup方法,来达到启动的时候创建一个名为当前程序集名称的互斥量,如果 _isRuning 为 False 的时候,表示这个互斥量名称已经被别的主线程创建了,也就是已经有一个 实例 在运行了,因为主线程的互斥量属于系统级。
2 . 进程判断
我们可以通过判断进程是否存在达到单实例模式的效果,但是缺点就是可以通过修改程序名或路径来达到重复启动的目的,不过一般用户情况下还是适用的。
Process current 1 2 3 4 5 6 7 8 9 10 11
| foreach (Process process in Process.GetProcessesByName (current.ProcessName)) { if (process.Id != current.Id) { if (process.MainModule.FileName == current.MainModule.FileName) { MessageBox.Show ("程序已经在运行,请勿重复启动!"); Environment.Exit (0); } } }
|
3 . 全局原子
Win32 系统中,为了实现信息共享,系统维护了一张全局原子表,用于保存字符串与之对应的标志符的组合。
我们需要引入 Kernel32.dll
1 2 3 4 5 6 7 8 9 10 11
| [System.Runtime.InteropServices.DllImport ("kernel32.dll")] public static extern UInt32 GlobalAddAtom (String lpString);
[System.Runtime.InteropServices.DllImport ("kernel32.dll")] public static extern UInt32 GlobalFindAtom (String lpString);
[System.Runtime.InteropServices.DllImport ("kernel32.dll")] public static extern UInt32 GlobalDeleteAtom (UInt32 nAtom);
|
然后在主函数入口写上这样类似于这样的判断
1 2 3 4 5 6 7 8 9 10
| if (GlobalFindAtom (Assembly.GetExecutingAssembly ().FullName) == 0) { GlobalAddAtom (Assembly.GetExecutingAssembly ().FullName); Run (); } else { MessageBox.Show ("程序已经在运行,请勿重复启动!"); Environment.Exit (0); }
|
因为系统查找全局原子的时候,如果没有找到,会返回一个数值为 0 的UInt32类型值,所以通过这里就可以判断如果为0,那么我们添加这个原子然后启动应用程序
注意:
全局原子有一个需要注意的地方,就是在退出应用程序的时候需要删除这个原子,要不然系统会一直保留这个原子,导致你的程序无法正常启动
1
| GlobalDeleteAtom(GlobalFindAtom(Assembly.GetExecutingAssembly().FullName));
|
活动已启动的程序窗口置顶
上面介绍了几种 .NET 程序的单实例实现方法,那么可能会有这样一个需求,就是当我们的应用程序已经启动,但是窗口状态处于最小化,或者隐藏在系统托盘的时候,我再次启动程序的时候,除了警告用户程序已启动,还需要把隐藏或者最小化的程序,最大化活动窗口置顶。
引入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| private const int SW_HIDE = 0; private const int SW_SHOWNORMAL = 1; private const int SW_SHOWMINIMIZED = 2; private const int SW_SHOWMAXIMIZED = 3; private const int SW_SHOWNOACTIVATE = 4; private const int SW_RESTORE = 9; private const int SW_SHOWDEFAULT = 10;
[System.Runtime.InteropServices.DllImport ("User32.dll")] private static extern bool OpenIcon (IntPtr hWnd);
[System.Runtime.InteropServices.DllImport ("User32.dll")] private static extern bool IsIconic (IntPtr hWnd);
[System.Runtime.InteropServices.DllImport ("User32.dll")] private static extern int SetForegroundWindow (IntPtr hWnd);
[System.Runtime.InteropServices.DllImport ("User32.dll")] private static extern bool ShowWindowAsync (IntPtr hWnd, int cmdShow);
|
实现方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public static Process GetRunning () { Process current = Process.GetCurrentProcess (); Process[] processes = Process.GetProcessesByName (current.ProcessName); foreach (Process process in processes) { if (process.Id != current.Id) { if (Assembly.GetExecutingAssembly ().Location.Replace ("/", "\\") == current.MainModule.FileName) { return process; } } } return null; }
public static void HandleShowRunning (Process instance) { ShowWindowAsync (instance.MainWindowHandle, SW_SHOWNORMAL); SetForegroundWindow (instance.MainWindowHandle); }
|
调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| private static Mutex mutex = null; protected override void OnStartup (StartupEventArgs e) { Process instance = GetRunning (); bool _isRuning = false; mutex = new Mutex (true, Assembly.GetExecutingAssembly ().FullName, out _isRuning); if (!_isRuning) { HandleShowRunning (instance); MessageBox.Show ("程序已经在运行,请勿重复启动!"); Environment.Exit (0); } base.OnStartup (e); }
|
这里我们依然以WPF为例,调用了Win32的一些方法,实现在启动前,先检测是否已经有运行的实例,如果有获得该进程的Handle然后传给 Win32 方法实现激活窗口