Run the current application as Single Instance and show the previous instance
Asked Answered
R

3

6

I just implemented this code that is guarding the Single Instance of the Application, in order to not run the application twice.

Now I am wondering how I can show the original Application process that is already running.

Here is my code in the program class:

static class Program
{
    [STAThread]
    static void Main()
    {
        const string appName = "MyappName";
        bool createdNew;
        mutex = new Mutex(true, appName, out createdNew);

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Form form = new Form1();

        if (!createdNew)
        {
            form.Show();  <<=========================== NOT WORKING
            form.Visible = true; <<===================== None
            form.TopMost = true; <<===================== of
            form.BringToFront(); <<===================== these working!
            form.WindowState = FormWindowState.Maximized;
            return;
        }
        Application.Run(form);
    }        private static Mutex mutex = null;
}
Rooker answered 27/5, 2018 at 13:21 Comment(3)
I was unable to reproduce the issue.Mcclees
you want to run multiple or just one ? check my answer !Glucoprotein
You have to talk to the already existing instance of your program and tell it to move its window into the foreground. That requires a lot more code, the gritty kind as well since process interop is always tricky to get right. One big reason to favor the support for this already built into the framework. Google "c# windowsformsapplicationbase startupnextinstance" to get decent hits. Might even show this post.Bronez
M
8

I propose you a different method, using a combination of the System.Threading.Mutex class and UIAutomation AutomationElement class.

A Mutex can be, as you already know, a simple string. You can assign an application a Mutex in the form of a GUID, but it can be anything else.
Let's assume this is the current Application Mutex:

string ApplicationMutex = "BcFFcd23-3456-6543-Fc44abcd1234";
//Or
string ApplicationMutex = "Global\BcFFcd23-3456-6543-Fc44abcd1234";

Note:
Use the "Global\" Prefix to define the scope of the Mutex. If no prefix is specified, the "Local\" prefix is assumed and used instead. This will prevent a single instance of the process when multiple desktops are active or Terminal Services is running on the server.

If we want to verify whether another running Process has already registered the same Mutex, we try to register our Mutex and if it fails, another instance of our Application is already running.
We let the user know that the Application supports only a single instance, then switch to the running process, showing its interface and finally exit the duplicate Application, disposing of the Mutex.

The method to activate a previous instance of the Application may vary based on the type of the Application, but only some details change.
We can use Process..GetProcesses() to retrieve a list of the running processes and verify if one of them has the same details as ours.

Here, you have a windowed Application (it has an UI), so it's already possible to filter the list, excluding those processes that do not have a MainWindowHandle.

Process[] windowedProcesses = 
    Process.GetProcesses().Where(p => p.MainWindowHandle != IntPtr.Zero).ToArray();

To identify the right one, we could test if the Process.ProcessName is the same.
But this name is tied to the executable name. If the file name changes (someone changes it for some reason), we will never identify the Process this way.

One possible way to identify the right Process is to test the Process.MainModule.FileVersionInfo.ProductName and check whether it's the same.

When found, it's possible to bring the original Application to front with an AutomationElement created using the MainWindowHandle of the identified Process.
The AutomationElement can automate different Patterns (sort of controls that provide automation functionalities for UI elements).
A WindowPattern allows to control a window-base control (the Platform is irrelevant, could be a WinForms' Form or a WPF's Window).

AutomationElement element = AutomationElement.FromHandle(process.MainWindowHandle);
WindowPattern wPattern = element.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern;
wPattern.SetWindowVisualState(WindowVisualState.Normal);

To use the UIAutomation functionalities, you have to add these refereneces in your Project:
- UIAutomationClient
- UIAutomationTypes

UPDATE:
Since the Application's Form might be hidden, Process.GetProcesses() will not find it's Window handle, thus AutomationElement.FromHandle() cannot be used to identify the Form Window.

A possible workaround, without dismissing the UIAutomation "pattern", is to register an Automation event, using Automation.AddAutomationEventHandler, which allows to receive a notification when an UI Automation events occurs, such as a new Window is about to be shown (a Program is run).

The event is registerd only if the Application needs to run as Single Instance. When the event is raised, the new Process AutomationElement Name (the Windows Title Text) is compared to the current and, if it's the same, the hidden Form will un-hide and show itself in Normal state.
As a fail-safe measure, we present an information MessageBox. The MessageBox caption has the same caption as the Application MainForm.
(Tested with a Form with its WindowsState set to Minimized and its Visible property set to false).


After the orginal Process has been brought to front, we just neeed to close the current thread and release the resources we created (mainly the Mutex, in this case).

using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Windows.Automation;
using System.Windows.Forms;

static class Program
{
    static Mutex mutex = null;

    [STAThread]
    static void Main()
    {
        Application.ThreadExit += ThreadOnExit;
        string applicationMutex = @"Global\BcFFcd23-3456-6543-Fc44abcd1234";
        mutex = new Mutex(true, applicationMutex);
        bool singleInstance = mutex.WaitOne(0, false);
        if (!singleInstance)
        {
            string appProductName = Process.GetCurrentProcess().MainModule.FileVersionInfo.ProductName;
            Process[] windowedProcesses = 
                Process.GetProcesses().Where(p => p.MainWindowHandle != IntPtr.Zero).ToArray();

            foreach (Process process in windowedProcesses.Where(p => p.MainModule.FileVersionInfo.ProductName == appProductName))
            {
                if (process.Id != Process.GetCurrentProcess().Id)
                {
                    AutomationElement wElement = AutomationElement.FromHandle(process.MainWindowHandle);
                    if (wElement.Current.IsOffscreen)
                    {
                        WindowPattern wPattern = wElement.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern;
                        #if DEBUG
                        WindowInteractionState state = wPattern.Current.WindowInteractionState;
                        Debug.Assert(!(state == WindowInteractionState.NotResponding), "The application is not responding");
                        Debug.Assert(!(state == WindowInteractionState.BlockedByModalWindow), "Main Window blocked by a Modal Window");
                        #endif
                        wPattern.SetWindowVisualState(WindowVisualState.Normal);
                        break;
                    }
                }
            }
            Thread.Sleep(200);
            MessageBox.Show("Application already running", "MyApplicationName",
                            MessageBoxButtons.OK, MessageBoxIcon.Information, 
                            MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification);
        }

        if (SingleInstance) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MyAppMainForm());
        }
        else {
            Application.ExitThread();
        }
    }
    private static void ThreadOnExit(object s, EventArgs e)
    {
        mutex.Dispose();
        Application.ThreadExit -= ThreadOnExit;
        Application.Exit();
    }
}

In the Application MainForm constructor:
(this is used in case the Application's Main Window is hidden when a new instance is run, hence the procedure in Program.cs cannot find its handle)

public partial class MyAppMainForm : Form
{
    public MyAppMainForm()
    {
        InitializeComponent();
        Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, 
                                             AutomationElement.RootElement, 
                                             TreeScope.Subtree, (uiElm, evt) =>
        {
            AutomationElement element = uiElm as AutomationElement;
            string windowText = element.Current.Name;
            if (element.Current.ProcessId != Process.GetCurrentProcess().Id && windowText == this.Text)
            {
                this.BeginInvoke(new MethodInvoker(() =>
                {
                    this.WindowState = FormWindowState.Normal;
                    this.Show();
                }));
            }
        });
    }    
}
Mahound answered 27/5, 2018 at 19:0 Comment(6)
Thanks, this works well when the application is already visible but not when it is hidden. I am getting "Application is not working " error when the application is hidden but still active as process and minimized to the notification icon.Rooker
So I am using this.Hide(); and notifyIcon1.Visible = true; when application is minimized or cross is clickedRooker
@Salihan Pamuk Well, you didn't specify this in your question. It's different situation. I'll see what I can do about it (I'm working right now).Mahound
I already found an article that helped my way out: sanity-free.org/143/… This works in all cases (minimized, hidden, invisible)Rooker
@Salihan Pamuk Allright, but be aware that with the method (11 years old) you linked, you're risking to see instances of your application stuck in memory. Anyway, I'll research a solution to bring a hidden application to view, that doesn't involve P/Invoking. I haven't tested in this situation the link posted by Hans Passant. But I'll try it.Mahound
@Salihan Pamuk I posted an update that tries to handle the hidden form case (see the Update note). Let me know if it's working for you.Mahound
G
3

Run Only One time :

static class Program
{    
    [STAThread]
    static void Main()
    {
        bool createdNew = true;
        using (Mutex mutex = new Mutex(true, "samplename", out createdNew))
        {
            if (createdNew)
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
                AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
                Application.Run(new Form1());
            }
            else
            {
                ProcessUtils.SetFocusToPreviousInstance("samplename");
            }
        }
    }

    private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
    }

    private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
    {
    }
}

ProcessUtils :

   public static class ProcessUtils
    {
        [DllImport("user32.dll", SetLastError = true)]
        static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool SetForegroundWindow(IntPtr hWnd);

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

        [DllImport("user32.dll")]
        static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        const int SW_RESTORE = 9;

        [DllImport("user32.dll")]
        static extern IntPtr GetLastActivePopup(IntPtr hWnd);

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


        public static void SetFocusToPreviousInstance(string windowCaption)
        {

            IntPtr hWnd = FindWindow(null, windowCaption);


            if (hWnd != null)
            {

                IntPtr hPopupWnd = GetLastActivePopup(hWnd);



                if (hPopupWnd != null && IsWindowEnabled(hPopupWnd))
                {
                    hWnd = hPopupWnd;
                }

                SetForegroundWindow(hWnd);


                if (IsIconic(hWnd))
                {
                    ShowWindow(hWnd, SW_RESTORE);
                }
            }
        }
    }

Normal Run :

static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}
Glucoprotein answered 27/5, 2018 at 13:50 Comment(0)
T
1

If you are still looking for an answer. There is a good example here that uses windows messages to restore the previous instance. It work even if the first instance is minimized contrary to FindWindow witch does not work in that case.

Timepleaser answered 16/6, 2020 at 18:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.