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

前端 未结 30 3179
耶瑟儿~
耶瑟儿~ 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:54

    The following code is my WCF named pipes solution to register a single-instance application. It's nice because it also raises an event when another instance attempts to start, and receives the command line of the other instance.

    It's geared toward WPF because it uses the System.Windows.StartupEventHandler class, but this could be easily modified.

    This code requires a reference to PresentationFramework, and System.ServiceModel.

    Usage:

    class Program
    {
        static void Main()
        {
            var applicationId = new Guid("b54f7b0d-87f9-4df9-9686-4d8fd76066dc");
    
            if (SingleInstanceManager.VerifySingleInstance(applicationId))
            {
                SingleInstanceManager.OtherInstanceStarted += OnOtherInstanceStarted;
    
                // Start the application
            }
        }
    
        static void OnOtherInstanceStarted(object sender, StartupEventArgs e)
        {
            // Do something in response to another instance starting up.
        }
    }
    

    Source Code:

    /// <summary>
    /// A class to use for single-instance applications.
    /// </summary>
    public static class SingleInstanceManager
    {
      /// <summary>
      /// Raised when another instance attempts to start up.
      /// </summary>
      public static event StartupEventHandler OtherInstanceStarted;
    
      /// <summary>
      /// Checks to see if this instance is the first instance running on this machine.  If it is not, this method will
      /// send the main instance this instance's startup information.
      /// </summary>
      /// <param name="guid">The application's unique identifier.</param>
      /// <returns>True if this instance is the main instance.</returns>
      public static bool VerifySingleInstace(Guid guid)
      {
        if (!AttemptPublishService(guid))
        {
          NotifyMainInstance(guid);
    
          return false;
        }
    
        return true;
      }
    
      /// <summary>
      /// Attempts to publish the service.
      /// </summary>
      /// <param name="guid">The application's unique identifier.</param>
      /// <returns>True if the service was published successfully.</returns>
      private static bool AttemptPublishService(Guid guid)
      {
        try
        {
          ServiceHost serviceHost = new ServiceHost(typeof(SingleInstance));
          NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
          serviceHost.AddServiceEndpoint(typeof(ISingleInstance), binding, CreateAddress(guid));
          serviceHost.Open();
    
          return true;
        }
        catch
        {
          return false;
        }
      }
    
      /// <summary>
      /// Notifies the main instance that this instance is attempting to start up.
      /// </summary>
      /// <param name="guid">The application's unique identifier.</param>
      private static void NotifyMainInstance(Guid guid)
      {
        NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
        EndpointAddress remoteAddress = new EndpointAddress(CreateAddress(guid));
        using (ChannelFactory<ISingleInstance> factory = new ChannelFactory<ISingleInstance>(binding, remoteAddress))
        {
          ISingleInstance singleInstance = factory.CreateChannel();
          singleInstance.NotifyMainInstance(Environment.GetCommandLineArgs());
        }
      }
    
      /// <summary>
      /// Creates an address to publish/contact the service at based on a globally unique identifier.
      /// </summary>
      /// <param name="guid">The identifier for the application.</param>
      /// <returns>The address to publish/contact the service.</returns>
      private static string CreateAddress(Guid guid)
      {
        return string.Format(CultureInfo.CurrentCulture, "net.pipe://localhost/{0}", guid);
      }
    
      /// <summary>
      /// The interface that describes the single instance service.
      /// </summary>
      [ServiceContract]
      private interface ISingleInstance
      {
        /// <summary>
        /// Notifies the main instance that another instance of the application attempted to start.
        /// </summary>
        /// <param name="args">The other instance's command-line arguments.</param>
        [OperationContract]
        void NotifyMainInstance(string[] args);
      }
    
      /// <summary>
      /// The implementation of the single instance service interface.
      /// </summary>
      private class SingleInstance : ISingleInstance
      {
        /// <summary>
        /// Notifies the main instance that another instance of the application attempted to start.
        /// </summary>
        /// <param name="args">The other instance's command-line arguments.</param>
        public void NotifyMainInstance(string[] args)
        {
          if (OtherInstanceStarted != null)
          {
            Type type = typeof(StartupEventArgs);
            ConstructorInfo constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
            StartupEventArgs e = (StartupEventArgs)constructor.Invoke(null);
            FieldInfo argsField = type.GetField("_args", BindingFlags.Instance | BindingFlags.NonPublic);
            Debug.Assert(argsField != null);
            argsField.SetValue(e, args);
    
            OtherInstanceStarted(null, e);
          }
        }
      }
    }
    
    0 讨论(0)
  • 2020-11-21 05:54

    I added a sendMessage Method to the NativeMethods Class.

    Apparently the postmessage method dosent work, if the application is not show in the taskbar, however using the sendmessage method solves this.

    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.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
        [DllImport("user32")]
        public static extern int RegisterWindowMessage(string message);
    }
    
    0 讨论(0)
  • 2020-11-21 05:55

    This code should go to the main method. Look at here for more information about the main method in WPF.

    [DllImport("user32.dll")]
    private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);
    
    private const int SW_SHOWMAXIMIZED = 3;
    
    static void Main() 
    {
        Process currentProcess = Process.GetCurrentProcess();
        var runningProcess = (from process in Process.GetProcesses()
                              where
                                process.Id != currentProcess.Id &&
                                process.ProcessName.Equals(
                                  currentProcess.ProcessName,
                                  StringComparison.Ordinal)
                              select process).FirstOrDefault();
        if (runningProcess != null)
        {
            ShowWindow(runningProcess.MainWindowHandle, SW_SHOWMAXIMIZED);
           return; 
        }
    }
    

    Method 2

    static void Main()
    {
        string procName = Process.GetCurrentProcess().ProcessName;
        // get the list of all processes by that name
    
        Process[] processes=Process.GetProcessesByName(procName);
    
        if (processes.Length > 1)
        {
            MessageBox.Show(procName + " already running");  
            return;
        } 
        else
        {
            // Application.Run(...);
        }
    }
    

    Note : Above methods assumes your process/application has a unique name. Because it uses process name to find if any existing processors. So, if your application has a very common name (ie: Notepad), above approach won't work.

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

    I found the simpler solution, similar to Dale Ragan's, but slightly modified. It does practically everything you need and based on the standard Microsoft WindowsFormsApplicationBase class.

    Firstly, you create SingleInstanceController class, which you can use in all other single-instance applications, which use Windows Forms:

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

    Then you can use it in your program as follows:

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

    Both the program and the SingleInstanceController_NET solution should reference Microsoft.VisualBasic . If you just want to reactivate the running application as a normal window when the user tries to restart the running program, the second parameter in the SingleInstanceController can be null. In the given example, the window is maximized.

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

    A new one that uses Mutex and IPC stuff, and also passes any command line arguments to the running instance, is WPF Single Instance Application.

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

    The code C# .NET Single Instance Application that is the reference for the marked answer is a great start.

    However, I found it doesn't handle very well the cases when the instance that already exist has a modal dialog open, whether that dialog is a managed one (like another Form such as an about box), or an unmanaged one (like the OpenFileDialog even when using the standard .NET class). With the original code, the main form is activated, but the modal one stays unactive, which looks strange, plus the user must click on it to keep using the app.

    So, I have create a SingleInstance utility class to handle all this quite automatically for Winforms and WPF applications.

    Winforms:

    1) modify the Program class like this:

    static class Program
    {
        public static readonly SingleInstance Singleton = new SingleInstance(typeof(Program).FullName);
    
        [STAThread]
        static void Main(string[] args)
        {
            // NOTE: if this always return false, close & restart Visual Studio
            // this is probably due to the vshost.exe thing
            Singleton.RunFirstInstance(() =>
            {
                SingleInstanceMain(args);
            });
        }
    
        public static void SingleInstanceMain(string[] args)
        {
            // standard code that was in Main now goes here
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
    

    2) modify the main window class like this:

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    
        protected override void WndProc(ref Message m)
        {
            // if needed, the singleton will restore this window
            Program.Singleton.OnWndProc(this, m, true);
    
            // TODO: handle specific messages here if needed
            base.WndProc(ref m);
        }
    }
    

    WPF:

    1) modify the App page like this (and make sure you set its build action to page to be able to redefine the Main method):

    public partial class App : Application
    {
        public static readonly SingleInstance Singleton = new SingleInstance(typeof(App).FullName);
    
        [STAThread]
        public static void Main(string[] args)
        {
            // NOTE: if this always return false, close & restart Visual Studio
            // this is probably due to the vshost.exe thing
            Singleton.RunFirstInstance(() =>
            {
                SingleInstanceMain(args);
            });
        }
    
        public static void SingleInstanceMain(string[] args)
        {
            // standard code that was in Main now goes here
            App app = new App();
            app.InitializeComponent();
            app.Run();
        }
    }
    

    2) modify the main window class like this:

    public partial class MainWindow : Window
    {
        private HwndSource _source;
    
        public MainWindow()
        {
            InitializeComponent();
        }
    
        protected override void OnSourceInitialized(EventArgs e)
        {
            base.OnSourceInitialized(e);
            _source = (HwndSource)PresentationSource.FromVisual(this);
            _source.AddHook(HwndSourceHook);
        }
    
        protected virtual IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            // if needed, the singleton will restore this window
            App.Singleton.OnWndProc(hwnd, msg, wParam, lParam, true, true);
    
            // TODO: handle other specific message
            return IntPtr.Zero;
        }
    

    And here is the utility class:

    using System;
    using System.ComponentModel;
    using System.Runtime.InteropServices;
    using System.Threading;
    
    namespace SingleInstanceUtilities
    {
        public sealed class SingleInstance
        {
            private const int HWND_BROADCAST = 0xFFFF;
    
            [DllImport("user32.dll")]
            private static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
    
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            private static extern int RegisterWindowMessage(string message);
    
            [DllImport("user32.dll")]
            private static extern bool SetForegroundWindow(IntPtr hWnd);
    
            public SingleInstance(string uniqueName)
            {
                if (uniqueName == null)
                    throw new ArgumentNullException("uniqueName");
    
                Mutex = new Mutex(true, uniqueName);
                Message = RegisterWindowMessage("WM_" + uniqueName);
            }
    
            public Mutex Mutex { get; private set; }
            public int Message { get; private set; }
    
            public void RunFirstInstance(Action action)
            {
                RunFirstInstance(action, IntPtr.Zero, IntPtr.Zero);
            }
    
            // NOTE: if this always return false, close & restart Visual Studio
            // this is probably due to the vshost.exe thing
            public void RunFirstInstance(Action action, IntPtr wParam, IntPtr lParam)
            {
                if (action == null)
                    throw new ArgumentNullException("action");
    
                if (WaitForMutext(wParam, lParam))
                {
                    try
                    {
                        action();
                    }
                    finally
                    {
                        ReleaseMutex();
                    }
                }
            }
    
            public static void ActivateWindow(IntPtr hwnd)
            {
                if (hwnd == IntPtr.Zero)
                    return;
    
                FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(hwnd));
            }
    
            public void OnWndProc(IntPtr hwnd, int m, IntPtr wParam, IntPtr lParam, bool restorePlacement, bool activate)
            {
                if (m == Message)
                {
                    if (restorePlacement)
                    {
                        WindowPlacement placement = WindowPlacement.GetPlacement(hwnd, false);
                        if (placement.IsValid && placement.IsMinimized)
                        {
                            const int SW_SHOWNORMAL = 1;
                            placement.ShowCmd = SW_SHOWNORMAL;
                            placement.SetPlacement(hwnd);
                        }
                    }
    
                    if (activate)
                    {
                        SetForegroundWindow(hwnd);
                        FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(hwnd));
                    }
                }
            }
    
    #if WINFORMS // define this for Winforms apps
            public void OnWndProc(System.Windows.Forms.Form form, int m, IntPtr wParam, IntPtr lParam, bool activate)
            {
                if (form == null)
                    throw new ArgumentNullException("form");
    
                if (m == Message)
                {
                    if (activate)
                    {
                        if (form.WindowState == System.Windows.Forms.FormWindowState.Minimized)
                        {
                            form.WindowState = System.Windows.Forms.FormWindowState.Normal;
                        }
    
                        form.Activate();
                        FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(form.Handle));
                    }
                }
            }
    
            public void OnWndProc(System.Windows.Forms.Form form, System.Windows.Forms.Message m, bool activate)
            {
                if (form == null)
                    throw new ArgumentNullException("form");
    
                OnWndProc(form, m.Msg, m.WParam, m.LParam, activate);
            }
    #endif
    
            public void ReleaseMutex()
            {
                Mutex.ReleaseMutex();
            }
    
            public bool WaitForMutext(bool force, IntPtr wParam, IntPtr lParam)
            {
                bool b = PrivateWaitForMutext(force);
                if (!b)
                {
                    PostMessage((IntPtr)HWND_BROADCAST, Message, wParam, lParam);
                }
                return b;
            }
    
            public bool WaitForMutext(IntPtr wParam, IntPtr lParam)
            {
                return WaitForMutext(false, wParam, lParam);
            }
    
            private bool PrivateWaitForMutext(bool force)
            {
                if (force)
                    return true;
    
                try
                {
                    return Mutex.WaitOne(TimeSpan.Zero, true);
                }
                catch (AbandonedMutexException)
                {
                    return true;
                }
            }
        }
    
        // NOTE: don't add any field or public get/set property, as this must exactly map to Windows' WINDOWPLACEMENT structure
        [StructLayout(LayoutKind.Sequential)]
        public struct WindowPlacement
        {
            public int Length { get; set; }
            public int Flags { get; set; }
            public int ShowCmd { get; set; }
            public int MinPositionX { get; set; }
            public int MinPositionY { get; set; }
            public int MaxPositionX { get; set; }
            public int MaxPositionY { get; set; }
            public int NormalPositionLeft { get; set; }
            public int NormalPositionTop { get; set; }
            public int NormalPositionRight { get; set; }
            public int NormalPositionBottom { get; set; }
    
            [DllImport("user32.dll", SetLastError = true)]
            private static extern bool SetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);
    
            [DllImport("user32.dll", SetLastError = true)]
            private static extern bool GetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);
    
            private const int SW_SHOWMINIMIZED = 2;
    
            public bool IsMinimized
            {
                get
                {
                    return ShowCmd == SW_SHOWMINIMIZED;
                }
            }
    
            public bool IsValid
            {
                get
                {
                    return Length == Marshal.SizeOf(typeof(WindowPlacement));
                }
            }
    
            public void SetPlacement(IntPtr windowHandle)
            {
                SetWindowPlacement(windowHandle, ref this);
            }
    
            public static WindowPlacement GetPlacement(IntPtr windowHandle, bool throwOnError)
            {
                WindowPlacement placement = new WindowPlacement();
                if (windowHandle == IntPtr.Zero)
                    return placement;
    
                placement.Length = Marshal.SizeOf(typeof(WindowPlacement));
                if (!GetWindowPlacement(windowHandle, ref placement))
                {
                    if (throwOnError)
                        throw new Win32Exception(Marshal.GetLastWin32Error());
    
                    return new WindowPlacement();
                }
                return placement;
            }
        }
    
        public static class FormUtilities
        {
            [DllImport("user32.dll")]
            private static extern IntPtr GetWindow(IntPtr hWnd, int uCmd);
    
            [DllImport("user32.dll", SetLastError = true)]
            private static extern IntPtr SetActiveWindow(IntPtr hWnd);
    
            [DllImport("user32.dll")]
            private static extern bool IsWindowVisible(IntPtr hWnd);
    
            [DllImport("kernel32.dll")]
            public static extern int GetCurrentThreadId();
    
            private delegate bool EnumChildrenCallback(IntPtr hwnd, IntPtr lParam);
    
            [DllImport("user32.dll")]
            private static extern bool EnumThreadWindows(int dwThreadId, EnumChildrenCallback lpEnumFunc, IntPtr lParam);
    
            private class ModalWindowUtil
            {
                private const int GW_OWNER = 4;
                private int _maxOwnershipLevel;
                private IntPtr _maxOwnershipHandle;
    
                private bool EnumChildren(IntPtr hwnd, IntPtr lParam)
                {
                    int level = 1;
                    if (IsWindowVisible(hwnd) && IsOwned(lParam, hwnd, ref level))
                    {
                        if (level > _maxOwnershipLevel)
                        {
                            _maxOwnershipHandle = hwnd;
                            _maxOwnershipLevel = level;
                        }
                    }
                    return true;
                }
    
                private static bool IsOwned(IntPtr owner, IntPtr hwnd, ref int level)
                {
                    IntPtr o = GetWindow(hwnd, GW_OWNER);
                    if (o == IntPtr.Zero)
                        return false;
    
                    if (o == owner)
                        return true;
    
                    level++;
                    return IsOwned(owner, o, ref level);
                }
    
                public static void ActivateWindow(IntPtr hwnd)
                {
                    if (hwnd != IntPtr.Zero)
                    {
                        SetActiveWindow(hwnd);
                    }
                }
    
                public static IntPtr GetModalWindow(IntPtr owner)
                {
                    ModalWindowUtil util = new ModalWindowUtil();
                    EnumThreadWindows(GetCurrentThreadId(), util.EnumChildren, owner);
                    return util._maxOwnershipHandle; // may be IntPtr.Zero
                }
            }
    
            public static void ActivateWindow(IntPtr hwnd)
            {
                ModalWindowUtil.ActivateWindow(hwnd);
            }
    
            public static IntPtr GetModalWindow(IntPtr owner)
            {
                return ModalWindowUtil.GetModalWindow(owner);
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题