Архив за день: 12.10.2021



WPF: Single Instance app реализация с помощью Mutex

В статье «Запускать только один экземпляр приложения» всё нормально работало, но минус всё же был — если приложение уже было запущено, то мы просто получали об этом уведомление в виде MessageBox. Дело в том, что мы не умели работать с другим процессом. В этот раз попробуем реализовать обращение к другому процессу и восстановление окна, если оно было свёрнуто. Давным давно мы тоже это делали, но там подключалась отдельная библиотека, исходник которой сейчас уже удалили и остались только её копии. Я не знаю причину, по которой она была удалена с оригинального сайта. Может что-то было в коде и эти ошибки (уязвимости?) не стали исправлять… На этот раз я буду использовать уже готовый класс из набора .NET, он называется Mutex. Я нашёл не так много примеров с его реализацией для моего случая, поэтому не уверен на счёт максимально эффективного кода. Но это решение мне понравилось, хотя кто-то в коментах писал, что у этого метода есть проблемы с производительностью. Тем не менее, распишу его здесь поподробнее.

Изменения для файла App.xaml.cs

Для примера мы будем генерировать уникальные строчки с помощью PowerShell утилиты guid.

 

Полный код файла App.xaml.cs будет такой:

using System;
using System.Threading;
using System.Windows;

namespace SingleInstance
{
  public partial class App
  {
    // The event mutex name.
    private const string UniqueEventName = "4380587d-6dde-47b6-bb23-68eb68a27ba5";

    // The unique mutex name.
    private const string UniqueMutexName = "5e81acba-2dd9-4a71-b8bd-625ab30309e4";

    // The event wait handle.
    private EventWaitHandle eventWaitHandle;

    // The mutex.
    private Mutex mutex;

    // The app on startup event
    private void AppOnStartup(object sender, StartupEventArgs e)
    {
      bool isOwned;
      mutex = new Mutex(true, UniqueMutexName, out isOwned);
      eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName);

      // So, R# would not give a warning that this variable is not used.
      GC.KeepAlive(mutex);

      if (isOwned)
      {
        // Spawn a thread which will be waiting for our event
        var thread = new Thread(
            () =>
            {
              while (eventWaitHandle.WaitOne())
              {
                Current.Dispatcher.BeginInvoke(
                        (Action)(() => ((MainWindow)Current.MainWindow).BringToForeground()));
              }
            });

        // It is important mark it as background otherwise it will prevent app from exiting.
        thread.IsBackground = true;

        thread.Start();
        return;
      }

      // Notify other instance so it could bring itself to foreground.
      eventWaitHandle.Set();

      // Terminate this instance.
      Shutdown();
    }
  }
}

Изменения для файла App.xaml

Чтобы запускалось не две копии приложения, а только одна, нам нужно обратить внимание на файл App.xaml, здесь свойство StartupUri дополняется подпиской на событие Startup, т.о. полный код этого файла теперь будет такой:

<Application x:Class="SingleInstance.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml"
             Startup="AppOnStartup">
    <Application.Resources>
         
    </Application.Resources>
</Application>

Изменения для файла MainWindow.xaml.cs

В файле App.xaml.cs мы обращались к методу BringToForeground, его необходимо описать в файле логики для главного окна MainWindow.xaml.cs:

using System.Windows;

namespace SingleInstance
{
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
    }

    // Brings main window to foreground.
    public void BringToForeground()
    {
      if (WindowState == WindowState.Minimized || Visibility == Visibility.Hidden)
      {
        Show();
        WindowState = WindowState.Normal;
      }

      // According to some sources these steps guarantee that an app will be brought to foreground.
      Activate();
      Topmost = true;
      Topmost = false;
      Focus();
    }
  }
}