让 WPF 应用程序单例化
在 WPF 程序的开发中,经常需要防止应用程序重复运行,但是 WPF 没有提供自带的解决方案,那么如何实现应用程序的单例化呢?
网上流行一种简单粗暴的方法,检测进程名,如果有同名的进程,就 Shutdown:
Process process = Process.GetCurrentProcess(); foreach (Process p in Process.GetProcessesByName(process.ProcessName)) { if (p.Id != process.Id) { Shutdown(); return; } }
这种方法非常不安全,但在一般使用环境中,我们自己的程序还是比较难与其他进程重名的,因此这种方法还是有一定的实用性。
更保险的方法是使用 Mutex:
Mutex mutex = new Mutex(true, Assembly.GetExecutingAssembly().GetName().Name, out bool createdNew); if (createdNew) { base.OnStartup(e); } else { Shutdown(); }
System.Threading.Mutex 专门用于进程同步,在这三个参数中,第一个表示获得 Mutex 的所有权;第二个是 Mutex 的名字,第三个表示是否新创建了 Mutex。由于名字可以是任意字符串,因此大大提高了程序的兼容性。
对于绝大多数单实例应用程序,上述两种方法都够用了,但如果想要构建类似于 Office、VS 等多窗口程序就不行了。如何降低应用程序开销、集中某些特性(例如创建单独的打印队列管理器)或集成不同窗口(例如平铺当前打开的文档窗口)?构建一个真正的单实例应用程序是最佳选择。
但最简单同时也是 WPF 团队推荐的方法是:使用 Windows Form 提供的内置支持,这一内置支持最初是用于 Visual Basic 应用程序的。这种方法在后台处理杂乱的问题。
1. 创建单实例应用程序封装器
首先添加 Microsoft.VisualBasic.dll 的引用,并从 Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase 类继承自定义类。WindowsFormsApplicationBase 类提供了三个用于管理实例的重要成员:
- IsSingleInstance 属性启用单实例应用程序。在构造函数中将该属性设置为 true。
- 当应用程序启动时触发的 OnStartup() 方法。此时重写该方法并创建 WPF 应用程序对象。
- 当另一个应用程序实例启动时触发的 OnStartupNextInstance() 方法。该方法提供了访问命令行参数的功能。此时,可调用 WPF 应用程序类中的方法来显示新的窗口,但不创建另一个应用程序对象。
下面是派生类的代码:
class SingleInstanceAppWrapper : WindowsFormsApplicationBase { public SingleInstanceAppWrapper() { // Enable single-instance mode. this.IsSingleInstance = true; } // Create the Wpf application class. private App app; protected override bool OnStartup(StartupEventArgs eventArgs) { app = new App(); app.Run(); return false; } // Direct multiple instances. protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs) { if (eventArgs.CommandLine.Count > 0) { app.Process(eventArgs.CommandLine[0]); } } }
当应用程序启动时,该类创建我们的平时使用的 App 类,该类可以添加一些方法,接收后续实例的启动参数,并显示新的窗口等等。
我们还需要切换应用程序的入口,由于需要在 App 类之前创建 SingleInstanceAppWrapper 类,所有应用程序必须使用传统的 Main() 方法来启动,而不能使用 App.xaml 文件:
public class Startup { [STAThread] public static void Main(string[] args) { SingleInstanceAppWrapper wrapper = new SingleInstanceAppWrapper(); wrapper.Run(args); } }
然后修改项目属性的“启动对象”为 Startup 类: