让 WPF 应用程序单例化

在 WPF 程序的开发中,经常需要防止应用程序重复运行,但是 WPF 没有提供自带的解决方案,那么如何实现应用程序的单例化呢?

网上流行一种简单粗暴的方法,检测进程名,如果有同名的进程,就 Shutdown:

Process process = Process.GetCurrentProcess();
foreach (Process p in Process.GetProcessesByName(process.ProcessName))
{
    if (p.Id != process.Id)
    {
        Shutdown();
        return;
    }
}
View Code

这种方法非常不安全,但在一般使用环境中,我们自己的程序还是比较难与其他进程重名的,因此这种方法还是有一定的实用性。

 

更保险的方法是使用 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]);
        }
    }
}
View Code

当应用程序启动时,该类创建我们的平时使用的 App 类,该类可以添加一些方法,接收后续实例的启动参数,并显示新的窗口等等。

我们还需要切换应用程序的入口,由于需要在 App 类之前创建 SingleInstanceAppWrapper 类,所有应用程序必须使用传统的 Main() 方法来启动,而不能使用 App.xaml 文件:

public class Startup
{
    [STAThread]
    public static void Main(string[] args)
    {
        SingleInstanceAppWrapper wrapper = new SingleInstanceAppWrapper();
        wrapper.Run(args);
    }
}
View Code

然后修改项目属性的“启动对象”为 Startup 类:

posted @ 2019-02-21 18:38  黑、猫  阅读(765)  评论(0编辑  收藏  举报