创建单实例WPF应用程序的正确方法是什么?

问题描述:

在.NET(而不是 Windows窗体或控制台)下使用C#和WPF,这是什么?创建只能作为单个实例运行的应用程序的正确方法?

Using C# and WPF under .NET (rather than Windows Forms or console), what is the correct way to create an application that can only be run as a single instance?

我知道它与某种称为互斥量的神话事物有关,我很少能找到一个烦人的人来阻止并解释其中的一个.

I know it has something to do with some mythical thing called a mutex, rarely can I find someone that bothers to stop and explain what one of these are.

代码还需要告知已经运行的实例用户试图启动第二个实例,并且还可能传递任何命令行参数(如果存在).

The code needs to also inform the already-running instance that the user tried to start a second one, and maybe also pass any command-line arguments if any existed.

这是一个非常好的文章关于Mutex解决方案.本文介绍的方法具有两个方面的优势.

Here is a very good article regarding the Mutex solution. The approach described by the article is advantageous for two reasons.

首先,它不需要依赖Microsoft.VisualBasic程序集.如果我的项目已经依赖于该程序集,那么我可能会提倡使用在另一个答案中显示的方法.但实际上,我不使用Microsoft.VisualBasic程序集,而我不想在项目中添加不必要的依赖项.

First, it does not require a dependency on the Microsoft.VisualBasic assembly. If my project already had a dependency on that assembly, I would probably advocate using the approach shown in another answer. But as it is, I do not use the Microsoft.VisualBasic assembly, and I'd rather not add an unnecessary dependency to my project.

第二,本文介绍了当用户尝试启动另一个实例时如何将应用程序的现有实例置于前台.这是一个非常不错的效果,此处所述的其他Mutex解决方案都无法解决.

Second, the article shows how to bring the existing instance of the application to the foreground when the user tries to start another instance. That's a very nice touch that the other Mutex solutions described here do not address.

自2014年8月1日起,我上面链接的文章仍然有效,但是一段时间未更新博客.这使我担心,最终它可能会消失,并且随之而来的是所倡导的解决方案.我在此转载本文的内容,以供后代参考.这些单词完全属于博客所有者,网址为 Sanity Free Coding .

As of 8/1/2014, the article I linked to above is still active, but the blog hasn't been updated in a while. That makes me worry that eventually it might disappear, and with it, the advocated solution. I'm reproducing the content of the article here for posterity. The words belong solely to the blog owner at Sanity Free Coding.

今天,我想重构一些禁止我的应用程序的代码 运行自身的多个实例.

Today I wanted to refactor some code that prohibited my application from running multiple instances of itself.

以前,我曾经使用过系统.Diagnostics.Process 搜索 进程列表中mymyapp.exe的实例.虽然有效,但 带来很多开销,我想要更清洁的东西.

Previously I had use System.Diagnostics.Process to search for an instance of my myapp.exe in the process list. While this works, it brings on a lot of overhead, and I wanted something cleaner.

知道我可以为此使用互斥体(但从来没有做过) 之前)我着手减少代码并简化生活.

Knowing that I could use a mutex for this (but never having done it before) I set out to cut down my code and simplify my life.

在我的应用程序主类中,我创建了一个名为

In the class of my application main I created a static named Mutex:

static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    ...
}

拥有一个命名的互斥锁可以使我们在 多个线程和进程,这正是我所寻找的魔力

Having a named mutex allows us to stack synchronization across multiple threads and processes which is just the magic I'm looking for.

Mutex.WaitOne 有一个重载为我们指定了时间 等待.由于我们实际上并不想同步我们的代码 (更多的只是检查它是否正在使用中),我们将重载与 两个参数: Mutex.WaitOne(Timespan超时,布尔值exitContext ). 如果可以输入,则等待返回true,否则返回false. 在这种情况下,我们根本不想等待.如果我们的互斥 使用,跳过它并继续前进,因此我们传入TimeSpan.Zero(等待0 毫秒),然后将exitContext设置为true,这样我们就可以退出 同步上下文,然后再尝试获取对其的锁定.使用 这样,我们将Application.Run代码包装在这样的东西中:

Mutex.WaitOne has an overload that specifies an amount of time for us to wait. Since we're not actually wanting to synchronizing our code (more just check if it is currently in use) we use the overload with two parameters: Mutex.WaitOne(Timespan timeout, bool exitContext). Wait one returns true if it is able to enter, and false if it wasn't. In this case, we don't want to wait at all; If our mutex is being used, skip it, and move on, so we pass in TimeSpan.Zero (wait 0 milliseconds), and set the exitContext to true so we can exit the synchronization context before we try to aquire a lock on it. Using this, we wrap our Application.Run code inside something like this:

static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    static void Main() {
        if(mutex.WaitOne(TimeSpan.Zero, true)) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
            mutex.ReleaseMutex();
        } else {
            MessageBox.Show("only one instance at a time");
        }
    }
}

因此,如果我们的应用程序正在运行,则WaitOne将返回false,我们将获得一个 消息框.

So, if our app is running, WaitOne will return false, and we'll get a message box.

我没有显示消息框,而是选择使用一点Win32来 通知我正在运行的实例有人忘记了它已经 运行(将自身置于所有其他窗口的顶部).到 为此,我使用了 PostMessage 向每个人广播自定义消息 窗口(自定义消息已在 RegisterWindowMessage 通过我正在运行的应用程序,这意味着只有我的应用程序知道 是的),然后我的第二个实例退出.正在运行的应用程序实例 将会收到该通知并进行处理.为了做到这一点,我 覆盖 WndProc 以我的主要形式听了我的习惯 通知.当我收到该通知时,我会设置表格的 将TopMost属性设置为true可以将其置于顶部.

Instead of showing a message box, I opted to utilize a little Win32 to notify my running instance that someone forgot that it was already running (by bringing itself to the top of all the other windows). To achieve this I used PostMessage to broadcast a custom message to every window (the custom message was registered with RegisterWindowMessage by my running application, which means only my application knows what it is) then my second instance exits. The running application instance would receive that notification and process it. In order to do that, I overrode WndProc in my main form and listened for my custom notification. When I received that notification I set the form's TopMost property to true to bring it up on top.

这就是我最后得到的:

  • Program.cs

static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    static void Main() {
        if(mutex.WaitOne(TimeSpan.Zero, true)) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
            mutex.ReleaseMutex();
        } else {
            // send our Win32 message to make the currently running instance
            // jump on top of all the other windows
            NativeMethods.PostMessage(
                (IntPtr)NativeMethods.HWND_BROADCAST,
                NativeMethods.WM_SHOWME,
                IntPtr.Zero,
                IntPtr.Zero);
        }
    }
}

  • NativeMethods.cs
  • NativeMethods.cs

// this class just wraps some Win32 stuff that we're going to use
internal class NativeMethods
{
    public const int HWND_BROADCAST = 0xffff;
    public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
    [DllImport("user32")]
    public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
    [DllImport("user32")]
    public static extern int RegisterWindowMessage(string message);
}

  • Form1.cs(部分为正面)
  • Form1.cs (front side partial)

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    protected override void WndProc(ref Message m)
    {
        if(m.Msg == NativeMethods.WM_SHOWME) {
            ShowMe();
        }
        base.WndProc(ref m);
    }
    private void ShowMe()
    {
        if(WindowState == FormWindowState.Minimized) {
            WindowState = FormWindowState.Normal;
        }
        // get our current "TopMost" value (ours will always be false though)
        bool top = TopMost;
        // make our form jump to the top of everything
        TopMost = true;
        // set it back to whatever it was
        TopMost = top;
    }
}