What is the correct way to create a single-instance WPF application?

前端 未结 30 3178
耶瑟儿~
耶瑟儿~ 2020-11-21 05:14

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 kno

相关标签:
30条回答
  • 2020-11-21 05:30

    Not using Mutex though, simple answer:

    System.Diagnostics;    
    ...
    string thisprocessname = Process.GetCurrentProcess().ProcessName;
    
    if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
                    return;
    

    Put it inside the Program.Main().
    Example:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    using System.Diagnostics;
    
    namespace Sample
    {
        static class Program
        {
            /// <summary>
            /// The main entry point for the application.
            /// </summary>
            [STAThread]
            static void Main()
            {
                //simple add Diagnostics namespace, and these 3 lines below 
                string thisprocessname = Process.GetCurrentProcess().ProcessName;
                if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
                    return;
    
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Sample());
            }
        }
    }
    

    You can add MessageBox.Show to the if-statement and put "Application already running".
    This might be helpful to someone.

    0 讨论(0)
  • 2020-11-21 05:30

    A time saving solution for C# Winforms...

    Program.cs:

    using System;
    using System.Windows.Forms;
    // needs reference to Microsoft.VisualBasic
    using Microsoft.VisualBasic.ApplicationServices;  
    
    namespace YourNamespace
    {
        public class SingleInstanceController : WindowsFormsApplicationBase
        {
            public SingleInstanceController()
            {
                this.IsSingleInstance = true;
            }
    
            protected override void OnStartupNextInstance(StartupNextInstanceEventArgs e)
            {
                e.BringToForeground = true;
                base.OnStartupNextInstance(e);
            }
    
            protected override void OnCreateMainForm()
            {
                this.MainForm = new Form1();
            }
        }
    
        static class Program
        {
            [STAThread]
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                string[] args = Environment.GetCommandLineArgs();
                SingleInstanceController controller = new SingleInstanceController();
                controller.Run(args);
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-21 05:32

    I can't find a short solution here so I hope someone will like this:

    UPDATED 2018-09-20

    Put this code in your 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 讨论(0)
  • 2020-11-21 05:33

    MSDN actually has a sample application for both C# and VB to do exactly this: http://msdn.microsoft.com/en-us/library/ms771662(v=VS.90).aspx

    The most common and reliable technique for developing single-instance detection is to use the Microsoft .NET Framework remoting infrastructure (System.Remoting). The Microsoft .NET Framework (version 2.0) includes a type, WindowsFormsApplicationBase, which encapsulates the required remoting functionality. To incorporate this type into a WPF application, a type needs to derive from it, and be used as a shim between the application static entry point method, Main, and the WPF application's Application type. The shim detects when an application is first launched, and when subsequent launches are attempted, and yields control the WPF Application type to determine how to process the launches.

    • For C# people just take a deep breath and forget about the whole 'I don't wanna include VisualBasic DLL'. Because of this and what Scott Hanselman says and the fact that this pretty much is the cleanest solution to the problem and is designed by people who know a lot more about the framework than you do.
    • From a usability standpoint the fact is if your user is loading an application and it is already open and you're giving them an error message like 'Another instance of the app is running. Bye' then they're not gonna be a very happy user. You simply MUST (in a GUI application) switch to that application and pass in the arguments provided - or if command line parameters have no meaning then you must pop up the application which may have been minimized.

    The framework already has support for this - its just that some idiot named the DLL Microsoft.VisualBasic and it didn't get put into Microsoft.ApplicationUtils or something like that. Get over it - or open up Reflector.

    Tip: If you use this approach exactly as is, and you already have an App.xaml with resources etc. you'll want to take a look at this too.

    0 讨论(0)
  • 2020-11-21 05:34

    Look at the folllowing code. It is a great and simple solution to prevent multiple instances of a WPF application.

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        Process thisProc = Process.GetCurrentProcess();
        if (Process.GetProcessesByName(thisProc.ProcessName).Length > 1)
        {
            MessageBox.Show("Application running");
            Application.Current.Shutdown();
            return;
        }
    
        var wLogin = new LoginWindow();
    
        if (wLogin.ShowDialog() == true)
        {
            var wMain = new Main();
            wMain.WindowState = WindowState.Maximized;
            wMain.Show();
        }
        else
        {
            Application.Current.Shutdown();
        }
    }
    
    0 讨论(0)
  • 2020-11-21 05:35

    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;
        }
    }
    
    0 讨论(0)
提交回复
热议问题