742 Stimmen

Was ist der richtige Weg, um eine einzelne Instanz WPF-Anwendung zu erstellen?

Verwendung von C# und WPF unter .NET (anstelle von Windows-Formulare oder Konsole), was ist der richtige Weg, um eine Anwendung zu erstellen, die nur als eine einzige Instanz ausgeführt werden kann?

Ich weiß, dass es etwas mit einem mythischen Ding namens Mutex zu tun hat, aber selten finde ich jemanden, der sich die Mühe macht, anzuhalten und zu erklären, was so etwas ist.

Der Code muss auch die bereits laufende Instanz darüber informieren, dass der Benutzer versucht hat, eine zweite Instanz zu starten, und vielleicht auch alle Befehlszeilenargumente übergeben, falls welche vorhanden sind.

17 Stimmen

Gibt die CLR nicht automatisch alle nicht freigegebenen Mutexe frei, wenn die Anwendung ohnehin beendet wird?

2 Stimmen

@Cocowalla: Der Finalisierer sollte die nicht verwalteten Mutexe entsorgen, es sei denn, er kann nicht wissen, ob die Mutex von der verwalteten Anwendung erstellt oder an eine bestehende angehängt wurde.

1 Stimmen

Es ist sinnvoll, nur eine Instanz Ihrer Anwendung zu haben. Aber die Übergabe von Argumenten an eine bereits existierende Anwendung erscheint mir ein wenig albern. Ich kann keinen Grund dafür sehen, dies zu tun. Wenn Sie eine Anwendung mit einer Dateierweiterung verknüpfen, sollten Sie so viele Anwendungen öffnen, wie Benutzer Dokumente öffnen wollen. Das ist das Standardverhalten, das jeder Benutzer erwarten würde.

4voto

Mikhail Semenov Punkte 953

Ich habe eine einfachere Lösung gefunden, die der von Dale Ragan ähnelt, aber leicht modifiziert ist. Sie tut praktisch alles, was Sie brauchen, und basiert auf der Standardklasse Microsoft WindowsFormsApplicationBase.

Zunächst erstellen Sie die Klasse SingleInstanceController, die Sie in allen anderen Single-Instance-Anwendungen verwenden können, die Windows Forms verwenden:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.VisualBasic.ApplicationServices;

namespace SingleInstanceController_NET
{
    public class SingleInstanceController
    : WindowsFormsApplicationBase
    {
        public delegate Form CreateMainForm();
        public delegate void StartNextInstanceDelegate(Form mainWindow);
        CreateMainForm formCreation;
        StartNextInstanceDelegate onStartNextInstance;
        public SingleInstanceController(CreateMainForm formCreation, StartNextInstanceDelegate onStartNextInstance)
        {
            // Set whether the application is single instance
            this.formCreation = formCreation;
            this.onStartNextInstance = onStartNextInstance;
            this.IsSingleInstance = true;

            this.StartupNextInstance += new StartupNextInstanceEventHandler(this_StartupNextInstance);                      
        }

        void this_StartupNextInstance(object sender, StartupNextInstanceEventArgs e)
        {
            if (onStartNextInstance != null)
            {
                onStartNextInstance(this.MainForm); // This code will be executed when the user tries to start the running program again,
                                                    // for example, by clicking on the exe file.
            }                                       // This code can determine how to re-activate the existing main window of the running application.
        }

        protected override void OnCreateMainForm()
        {
            // Instantiate your main application form
            this.MainForm = formCreation();
        }

        public void Run()
        {
            string[] commandLine = new string[0];
            base.Run(commandLine);
        }
    }
}

Dann können Sie es wie folgt in Ihrem Programm verwenden:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using SingleInstanceController_NET;

namespace SingleInstance
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        static Form CreateForm()
        {
            return new Form1(); // Form1 is used for the main window.
        }

        static void OnStartNextInstance(Form mainWindow) // When the user tries to restart the application again,
                                                         // the main window is activated again.
        {
            mainWindow.WindowState = FormWindowState.Maximized;
        }
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);            
            SingleInstanceController controller = new SingleInstanceController(CreateForm, OnStartNextInstance);
            controller.Run();         
        }
    }
}

Sowohl das Programm als auch die SingleInstanceController_NET-Lösung sollten auf Microsoft.VisualBasic verweisen. Wenn Sie die laufende Anwendung nur als normales Fenster reaktivieren wollen, wenn der Benutzer versucht, das laufende Programm neu zu starten, kann der zweite Parameter im SingleInstanceController null sein. Im angegebenen Beispiel ist das Fenster maximiert.

4voto

Sergey Aldoukhov Punkte 21696

Hier ist, was ich verwende. Es kombiniert Prozessaufzählung, um das Umschalten und Mutex zum Schutz vor "aktiven Klickern" durchzuführen:

public partial class App
{
    [DllImport("user32")]
    private static extern int OpenIcon(IntPtr hWnd);

    [DllImport("user32.dll")]
    private static extern bool SetForegroundWindow(IntPtr hWnd);

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        var p = Process
           .GetProcessesByName(Process.GetCurrentProcess().ProcessName);
            foreach (var t in p.Where(t => t.MainWindowHandle != IntPtr.Zero))
            {
                OpenIcon(t.MainWindowHandle);
                SetForegroundWindow(t.MainWindowHandle);
                Current.Shutdown();
                return;
            }

            // there is a chance the user tries to click on the icon repeatedly
            // and the process cannot be discovered yet
            bool createdNew;
            var mutex = new Mutex(true, "MyAwesomeApp", 
               out createdNew);  // must be a variable, though it is unused - 
            // we just need a bit of time until the process shows up
            if (!createdNew)
            {
                Current.Shutdown();
                return;
            }

            new Bootstrapper().Run();
        }
    }

0 Stimmen

Was ist Bootstrapper hier?

4voto

Matt Davison Punkte 1524

Sie sollten niemals einen benannten Mutex verwenden, um eine Einzelinstanzanwendung zu implementieren (zumindest nicht für Produktionscode). Bösartiger Code kann leicht DoS ( Denial of Service ) deinen Arsch...

12 Stimmen

"Sie sollten niemals einen benannten Mutex verwenden" - sagen Sie niemals nie. Wenn bösartiger Code auf meinem Rechner läuft, bin ich wahrscheinlich schon am Ende.

1 Stimmen

Eigentlich muss es nicht einmal bösartiger Code sein. Es könnte sich auch nur um eine versehentliche Namenskollision handeln.

1 Stimmen

Die bessere Frage ist, aus welchem Grund Sie dieses Verhalten wünschen. Entwerfen Sie Ihre Anwendung nicht als Einzelinstanzanwendung=). Ich weiß, das ist eine lahme Antwort, aber vom Standpunkt des Designs ist es fast immer die richtige Antwort. Ohne mehr über die Anwendung zu wissen, ist es schwer, viel mehr zu sagen.

4voto

Eric Ouellet Punkte 9965

Update 2017-01-25. Nachdem ich einige Dinge ausprobiert hatte, entschied ich mich für die VisualBasic.dll, da sie einfacher ist und besser funktioniert (zumindest für mich). Ich lasse meine vorherige Antwort nur als Referenz...

Nur als Referenz, dies ist, wie ich ohne die Übergabe von Argumenten (die ich keinen Grund, so zu tun finden kann... Ich meine eine einzelne App mit Argumenten, die von einer Instanz zu einer anderen weitergegeben werden müssen). Wenn Dateizuordnung erforderlich ist, dann sollte eine App (pro Benutzer Standard-Erwartung) für jedes Dokument instanziiert werden. Wenn Sie Argumente an eine bestehende Anwendung weitergeben müssen, würde ich wohl eine vb dll verwenden.

Ich bevorzuge es, keine Args zu übergeben (nur eine einzelne Instanz der Anwendung), keine neue Fenstermeldung zu registrieren und die Nachrichtenschleife nicht zu überschreiben, wie in Matt Davis' Lösung definiert. Obwohl es keine große Sache ist, eine VisualBasic dll hinzuzufügen, ziehe ich es vor, keinen neuen Verweis hinzuzufügen, nur um eine Einzelinstanzanwendung zu erstellen. Außerdem ziehe ich es vor, eine neue Klasse mit Main zu instanziieren, anstatt Shutdown von App.Startup override aufzurufen, um sicherzustellen, dass die App so bald wie möglich beendet wird.

In der Hoffnung, dass es jemandem gefällt... oder ein bisschen inspiriert :-)

Die Startklasse des Projekts sollte als 'SingleInstanceApp' festgelegt werden.

public class SingleInstanceApp
{
    [STAThread]
    public static void Main(string[] args)
    {
        Mutex _mutexSingleInstance = new Mutex(true, "MonitorMeSingleInstance");

        if (_mutexSingleInstance.WaitOne(TimeSpan.Zero, true))
        {
            try
            {
                var app = new App();
                app.InitializeComponent();
                app.Run();

            }
            finally
            {
                _mutexSingleInstance.ReleaseMutex();
                _mutexSingleInstance.Close();
            }
        }
        else
        {
            MessageBox.Show("One instance is already running.");

            var processes = Process.GetProcessesByName(Assembly.GetEntryAssembly().GetName().Name);
            {
                if (processes.Length > 1)
                {
                    foreach (var process in processes)
                    {
                        if (process.Id != Process.GetCurrentProcess().Id)
                        {
                            WindowHelper.SetForegroundWindow(process.MainWindowHandle);
                        }
                    }
                }
            }
        }
    }
}

WindowHelper:

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;

namespace HQ.Util.Unmanaged
{
    public class WindowHelper
    {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SetForegroundWindow(IntPtr hWnd);

4voto

Deniz Punkte 421

Ich kann keine Kurzlösung und ich hoffe, dass es jemandem gefallen wird:

AKTUALISIERT 2018-09-20

Fügen Sie diesen Code in Ihr Program.cs :

using System.Diagnostics;

static void Main()
{
    Process thisProcess = Process.GetCurrentProcess();
    Process[] allProcesses = Process.GetProcessesByName(thisProcess.ProcessName);
    if (allProcesses.Length > 1)
    {
        // Don't put a MessageBox in here because the user could spam this MessageBox.
        return;
    }

    // Optional code. If you don't want that someone runs your ".exe" with a different name:

    string exeName = AppDomain.CurrentDomain.FriendlyName;
    // in debug mode, don't forget that you don't use your normal .exe name.
    // Debug uses the .vshost.exe.
    if (exeName != "the name of your executable.exe") 
    {
        // You can add a MessageBox here if you want.
        // To point out to users that the name got changed and maybe what the name should be or something like that^^ 
        MessageBox.Show("The executable name should be \"the name of your executable.exe\"", 
            "Wrong executable name", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }

    // Following code is default code:
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
}

0 Stimmen

Dies führt zu einer Wettlaufsituation. Muss eine Mutex verwenden.

1 Stimmen

Es gibt keine Garantie dafür, dass dies funktioniert, wenn Sie zwei Instanzen gleichzeitig in Betrieb nehmen. So wie das Aktualisieren einer Variablen von zwei verschiedenen Threads aus. Eine knifflige und riskante Angelegenheit. Nutze die Macht, Luke :)

0 Stimmen

@georgiosd Ah, ich verstehe, was du meinst. Zum Beispiel wenn jemand die .exe startet und den Namen ändert. Ja, das wäre eine Möglichkeit, sie öfter zu starten, aber normalerweise funktioniert die .exe nicht, wenn der Name geändert wurde. Ich werde meine Antwort aktualisieren^^ Danke Luke :D für den Hinweis :)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X