Как правильно создать однократно запускаемое WPF-приложение?
Решение через Mutex с передачей фокуса существующему окну
Самый надёжный и распространённый способ создания однократно запускаемого WPF-приложения — использование Mutex (мьютекса) в сочетании с механизмом отправки сообщений для фокусировки уже открытого окна. Этот подход гарантирует, что даже при одновременном запуске нескольких процессов только один из них продолжит работу.
Вот полный пример реализации в файле App.xaml.cs:
using System;
using System.Threading;
using System.Windows;
public partial class App : Application
{
private Mutex _mutex;
private const string AppMutexName = "UniqueAppName-{8E4F5A6B-1C2D-3E4F-5A6B-7C8D9E0F1A2B}"; // GUID для уникальности
[STAThread]
public static void Main()
{
bool createdNew;
using (_mutex = new Mutex(true, AppMutexName, out createdNew))
{
if (createdNew)
{
// Первый запуск — создаём приложение
var app = new App();
app.InitializeComponent();
app.Run();
}
else
{
// Повторный запуск — фокусируем существующее окно
// Используем User32 API для отправки сообщения
var handle = FindWindow(null, "MainWindowTitle"); // Замените на заголовок вашего окна
if (handle != IntPtr.Zero)
{
ShowWindow(handle, SW_RESTORE);
SetForegroundWindow(handle);
}
}
}
}
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
private const int SW_RESTORE = 9;
}
Пояснение работы
- Mutex (мьютекс) — системный объект синхронизации. При создании с флагом
trueпервый запуск получает право на выполнение, а последующие — нет. - GUID в имени мьютекса гарантирует уникальность и предотвращает конфликты с другими приложениями.
- API User32 (
FindWindow,ShowWindow,SetForegroundWindow) используется для поиска и активации уже открытого окна. - Метод
Mainс атрибутомSTAThreadобязателен для WPF-приложений.
Альтернативный способ с передачей аргументов через канал
Если вам нужно передать аргументы командной строки в уже запущенное приложение (например, открыть файл), используйте NamedPipeServerStream или WindowsFormsApplicationBase из Microsoft.VisualBasic.dll:
// Добавьте ссылку на Microsoft.VisualBasic
using Microsoft.VisualBasic.ApplicationServices;
public class SingleInstanceApp : WindowsFormsApplicationBase
{
public SingleInstanceApp()
{
this.IsSingleInstance = true;
this.StartupNextInstance += OnStartupNextInstance;
}
private void OnStartupNextInstance(object sender, StartupNextInstanceEventArgs e)
{
// Обрабатываем аргументы (например, открываем файл)
var mainWindow = Application.Current.MainWindow;
if (mainWindow != null)
{
mainWindow.Show();
mainWindow.Activate();
}
}
}
// В App.xaml.cs
public partial class App : Application
{
[STAThread]
public static void Main()
{
var app = new SingleInstanceApp();
app.Run(Environment.GetCommandLineArgs());
}
}
Рекомендации:
- Всегда обрабатывайте исключения при работе с мьютексом (например,
AbandonedMutexException). - Для отладки используйте уникальное имя мьютекса, отличное от финального.
- Не забывайте вызывать
_mutex.ReleaseMutex()при завершении приложения.
Простое решение через EventWaitHandle
Если вам не нужна передача фокуса на существующее окно, а достаточно просто блокировать повторный запуск с выводом сообщения, можно использовать EventWaitHandle в режиме EventResetMode.AutoReset. Этот подход проще и не требует P/Invoke.
public partial class App : Application
{
private static EventWaitHandle _waitHandle;
private const string EventName = "UniqueAppEvent-{B3C4D5E6-F7A8-9B0C-1D2E-3F4A5B6C7D8E}";
protected override void OnStartup(StartupEventArgs e)
{
bool createdNew;
_waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, EventName, out createdNew);
if (!createdNew)
{
MessageBox.Show("Приложение уже запущено!", "Ошибка",
MessageBoxButton.OK, MessageBoxImage.Warning);
Application.Current.Shutdown();
return;
}
base.OnStartup(e);
}
protected override void OnExit(ExitEventArgs e)
{
_waitHandle?.Set(); // Сигнализируем о завершении
_waitHandle?.Dispose();
base.OnExit(e);
}
}
Плюсы и минусы
- + Простая реализация без WinAPI.
- + Автоматическая очистка ресурсов при завершении процесса.
- - Не позволяет передать фокус на существующее окно.
- - Менее надёжен при аварийном завершении (может остаться "зависший" дескриптор).
Этот вариант подходит для простых утилит, где достаточно предотвратить множественный запуск без необходимости взаимодействия с уже работающим экземпляром.
Ответов пока нет.
Войдите чтобы оставить ответ