创建单实例 WPF 应用程序的正确方法是什么? [英] What is the correct way to create a single-instance WPF application?

查看:31
本文介绍了创建单实例 WPF 应用程序的正确方法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我知道它与某种叫做互斥锁的神秘事物有关,我很少能找到有人费心停下来解释其中一个是什么.

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

解决方案

这里有一篇很好的文章 关于 Mutex 解决方案.文章中描述的方法具有优势,原因有两个.

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

其次,本文展示了当用户尝试启动另一个实例时如何将应用程序的现有实例置于前台.这是这里描述的其他互斥体解决方案没有解决的非常好的问题.

<小时>

更新

截至 2014 年 8 月 1 日,我上面链接的文章仍然有效,但博客已经有一段时间没有更新了.这让我担心它最终可能会消失,随之而来的是提倡的解决方案.我在这里复制文章的内容以供后人使用.这些词仅属于 Sanity Free Coding 上的博客所有者.

<块引用>

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

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

知道我可以为此使用互斥锁(但从未这样做过之前)我开始减少我的代码并简化我的生活.

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

静态类程序{static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");[STAThread]...}

<块引用>

拥有一个命名的互斥体允许我们跨堆栈同步多线程和进程,这正是我正在寻找的魔法

Mutex.WaitOne 有一个为我们指定一定时间的重载等待.因为我们实际上并不想同步我们的代码(更多只是检查它当前是否正在使用)我们使用重载两个参数:Mutex.WaitOne(Timespan timeout, bool exitContext).如果可以进入则等待返回 true,否则返回 false.在这种情况下,我们根本不想等待;如果我们的互斥量被使用,跳过它,然后继续,所以我们传入 TimeSpan.Zero(等待 0毫秒),并将 exitContext 设置为 true 以便我们可以退出在我们尝试获取锁定之前的同步上下文.使用这样,我们将 Application.Run 代码包装在如下内容中:

静态类程序{static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");[STAThread]静态无效主(){if(mutex.WaitOne(TimeSpan.Zero, true)) {Application.EnableVisualStyles();Application.SetCompatibleTextRenderingDefault(false);Application.Run(new Form1());mutex.ReleaseMutex();} 别的 {MessageBox.Show("一次只有一个实例");}}}

<块引用>

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

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

这是我最后的结果:

  • 程序.cs

静态类程序{static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");[STAThread]静态无效主(){if(mutex.WaitOne(TimeSpan.Zero, true)) {Application.EnableVisualStyles();Application.SetCompatibleTextRenderingDefault(false);Application.Run(new Form1());mutex.ReleaseMutex();} 别的 {//发送我们的 Win32 消息以创建当前正在运行的实例//跳到所有其他窗口的顶部NativeMethods.PostMessage((IntPtr)NativeMethods.HWND_BROADCAST,NativeMethods.WM_SHOWME,IntPtr.零,IntPtr.零);}}}

<块引用>

  • NativeMethods.cs

//这个类只是包装了一些我们将要使用的 Win32 东西内部类 NativeMethods{公共常量 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 : Form{公共 Form1(){初始化组件();}protected override void WndProc(ref Message m){if(m.Msg == NativeMethods.WM_SHOWME) {给我看看();}base.WndProc(ref m);}私人无效 ShowMe(){if(WindowState == FormWindowState.Minimized) {WindowState = FormWindowState.Normal;}//获取我们当前的TopMost"值(不过我们的值总是假的)bool top = TopMost;//让我们的表单跳转到所有内容的顶部TopMost = 真;//把它设置回原来的样子TopMost = 顶部;}}

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.

解决方案

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

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.

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.


UPDATE

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.

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 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");
        }
    }
}

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

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.

Here is what I ended up with:

  • 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

// 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 (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;
    }
}

这篇关于创建单实例 WPF 应用程序的正确方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆