Запустите текущее приложение как единичный экземпляр и покажите предыдущий экземпляр

Я только что реализовал этот код, который защищает Единственный Экземпляр Приложения, чтобы не запускать приложение дважды.

Теперь мне интересно, как я могу показать оригинальный процесс приложения, который уже запущен.

Вот мой код в классе программы:

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

2 ответа

Решение

Я предлагаю вам другой метод, использующий комбинацию класса System.Threading.Mutex и класса UIAutomation AutomationElement.

Mutex может быть, как вы уже знаете, простая строка. Вы можете назначить приложению Mutex в виде GUID, но это может быть что угодно еще.
Давайте предположим, что это текущее приложение Mutex:

string ApplicationMutex = "BcFFcd23-3456-6543-Fc44abcd1234";

Если мы хотим увидеть, если другой запущенный процесс уже зарегистрировал такой же Mutex мы пытаемся зарегистрировать наш Mutex и если это не удается, другой экземпляр нашего приложения уже запущен.
Мы могли бы сообщить пользователю, что приложение поддерживает только один экземпляр, затем переключиться на запущенный процесс, показывающий его интерфейс, и, наконец, выйти из дубликата приложения, удалив Mutex,

Способ активации предыдущего экземпляра Приложения может различаться в зависимости от типа Приложения, но меняются только некоторые детали.
Мы можем использовать Process..GetProcesses(), чтобы получить список запущенных процессов и проверить, имеет ли один из них те же данные, что и у нас.

Здесь у вас есть оконное приложение (оно имеет пользовательский интерфейс), так что уже можно отфильтровать список, исключая те процессы, у которых нет MainWindowHandle.

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

Чтобы определить правильный, мы могли бы проверить, совпадает ли Process.ProcessName.
Но это имя связано с именем исполняемого файла. Если имя файла изменяется (кто-то меняет его по какой-то причине), мы никогда не будем идентифицировать Процесс таким образом.

Один из возможных способов определения правильного процесса - это проверить Process.MainModule.FileVersionInfo.ProductName и убедиться, что он такой же.

Найдя его, можно вывести оригинальное приложение на UIAutomationAutomationElement, созданный с использованием MainWindowHandle идентифицированного процесса.
AutomationElement может автоматизировать различные шаблоны (своего рода элементы управления, которые предоставляют функции автоматизации для элементов пользовательского интерфейса).
WindowPattern позволяет управлять оконным базовым элементом управления (форма. Платформа не имеет значения, может быть WinForms или же WPF).

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

Чтобы использовать UIAutomation функциональности, вы должны добавить эти ссылки в свой проект:
- UIAutomationClient
- UIAutomationTypes

ОБНОВИТЬ:
Поскольку приложение Form может быть скрыт, Process.GetProcesses() не найдет это дескриптор окна, таким образом AutomationElement.FromHandle() не может быть использован для идентификации Form Окно.

Возможный обходной путь, не отклоняя "шаблон" UIAutomation, состоит в регистрации Automation.AddAutomationEventHandler, который позволяет получать уведомление, когда происходят события автоматизации пользовательского интерфейса, такие как новое окно, которое должно быть показано (программа запущена).

Событие регистрируется только в том случае, если приложение идентифицирует себя как работающее как единичный экземпляр. Когда событие поднято, новый процесс AutomationElement Имя (текст заголовка Windows) сравнивается с текущим, и если оно совпадает, скрытая форма будет скрыта и отобразится в обычном состоянии.
В качестве меры безопасности мы представляем информацию MessageBox, MessageBox заголовок имеет тот же заголовок, что и приложение MainForm,
(Проверено с формой с WindowsState установлен в Minimized И его Visible свойство установлено в false).


После того, как оригинальный процесс был выведен на передний план, нам просто нужно закрыть текущий поток и освободить созданные нами ресурсы (в основном, Mutex, в данном случае).

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 += new EventHandler(ThreadOnExit);
        string ApplicationMutex = "BcFFcd23-3456-6543-Fc44abcd1234";
        mutex = new Mutex(true, ApplicationMutex);
        bool SingleInstance = mutex.WaitOne(0, false);
        if (!SingleInstance)
        {
            MessageBox.Show("Application already running", "[MyApplicationMainFormText]", 
                            MessageBoxButtons.OK, MessageBoxIcon.Information);
            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 element = AutomationElement.FromHandle(process.MainWindowHandle);
                    if (!element.Current.IsOffscreen)
                    {
                        WindowPattern WPattern = element.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern;
                        WindowInteractionState state = WPattern.Current.WindowInteractionState;
                        WPattern.SetWindowVisualState(WindowVisualState.Normal);
                        break;
                    }
                }
            }
        }

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

В приложении MainForm конструктор:

public partial class MyAppMainForm : Form
{
    public MyAppMainForm()
    {
        InitializeComponent();
        Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, 
                                                AutomationElement.RootElement, 
                                                TreeScope.Subtree, (UIElm, evt) =>
        {
            AutomationElement element = UIElm as AutomationElement;
            string AppText = element.Current.Name;
            if (element.Current.ProcessId != Process.GetCurrentProcess().Id && AppText == this.Text)
            {
                this.Invoke((MethodInvoker)delegate {
                    this.Visible = true;
                    this.WindowState = FormWindowState.Normal;
                    this.Show();
                });
            }
        });
    }    
}

Запускать только один раз:

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

Нормальный прогон:

static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}

Если вы все еще ищете ответ. Существует хороший пример здесь, который использует окна сообщений для восстановления предыдущего экземпляра. Он работает, даже если первый экземпляр свернут, в отличие от FindWindow, ведьма в этом случае не работает.

Другие вопросы по тегам