Я предлагаю вам другой метод, использующий комбинацию класса System.Threading.Mutex и UIAutomation AutomationElement класса.
A Mutex
может быть, как вы уже знаете, простой строкой.Вы можете назначить приложению Mutex в форме GUID, но это может быть что угодно еще.
Предположим, что это текущее приложение Mutex
:
string ApplicationMutex = "BcFFcd23-3456-6543-Fc44abcd1234";
//Or
string ApplicationMutex = "Global\BcFFcd23-3456-6543-Fc44abcd1234";
Примечание :
Используйте префикс "Global\"
, чтобы определить область действия Mutex.Если префикс не указан, вместо него используется префикс "Local\"
.Это предотвратит один экземпляр процесса, когда активны несколько рабочих столов или на сервере запущены службы терминалов.
Если мы хотим проверить, зарегистрировал ли другой работающий Процесс тот же самый Mutex
, мы попытаемся зарегистрировать наш Mutex
, и в случае сбоя другой экземпляр нашего Приложения уже запущен.
МыСообщите пользователю, что приложение поддерживает только один экземпляр, затем переключитесь на запущенный процесс, показывая его интерфейс и, наконец, выйдите из дубликата приложения, удалив Mutex
.
Способ активации предыдущего экземпляра Приложения может различаться в зависимости от типа Приложения, но меняются только некоторые детали.
Мы можем использовать Process..GetProcesses () чтобы получить список запущенных процессов и проверить, имеет ли один из них те же данные, что и у нас.
Здесь у вас есть оконное приложение (оно имеет пользовательский интерфейс), поэтому уже можно отфильтровать список, исключая те процессы, у которых нет MainWindowHandle .
Process[] windowedProcesses =
Process.GetProcesses().Where(p => p.MainWindowHandle != IntPtr.Zero).ToArray();
Чтобы определить правильный, мы могли бы проверить, совпадает ли Process.ProcessName .
Но это имя связано с исполняемым файломназвание.Если имя файла изменяется (кто-то меняет его по какой-то причине), мы никогда не будем идентифицировать Процесс таким образом.
Один из возможных способов определения правильного процесса - проверить Process.MainModule.FileVersionInfo.ProductName и проверить, совпадает ли он.
При обнаружении можно вывести оригинальное приложение на передний план с помощью UIAutomation
AutomationElement
, созданного с использованием MainWindowHandle
идентифицированного процесса.
AutomationElement
может автоматизировать различные Patterns (вид элементов управления, обеспечивающих функциональные возможности автоматизации для элементов пользовательского интерфейса).
A WindowPattern позволяет управлять элементом управления на основе окна (Платформа не имеет значения, может быть формой WinForms илиокно WPF).
AutomationElement element = AutomationElement.FromHandle(process.MainWindowHandle);
WindowPattern wPattern = element.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern;
wPattern.SetWindowVisualState(WindowVisualState.Normal);
Чтобы использовать функции UIAutomation
, вы должны добавить эти ссылки в свой проект:
- UIAutomationClient
- UIAutomationTypes
ОБНОВЛЕНИЕ:
Поскольку форма приложения может быть скрыта, 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 += 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.Close();
mutex.Dispose();
Application.ThreadExit -= ThreadOnExit;
Application.Exit();
}
}
В приложении MainForm
конструктор:
(используется, если главное окно приложения скрыто при запуске нового экземпляра, поэтому процедура в Program.cs
не может найти свой дескриптор)
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();
}));
}
});
}
}