Как правильно создать приложение WPF с одним экземпляром? - PullRequest
607 голосов
/ 21 августа 2008

Используя C # и WPF под .NET (а не Windows Forms или консоль), как правильно создать приложение, которое можно запустить только как один экземпляр?

Я знаю, что это как-то связано с какой-то мифической штукой, называемой мьютексом, редко я могу найти кого-то, кто мешает остановиться и объяснить, что из этого есть.

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

Ответы [ 35 ]

488 голосов
/ 07 февраля 2009

Вот очень хорошая статья , касающаяся решения Mutex. Описанный в статье подход выгоден по двум причинам.

Во-первых, он не требует зависимости от сборки Microsoft.VisualBasic. Если бы мой проект уже зависел от этой сборки, я бы, вероятно, рекомендовал использовать подход , показанный в другом ответе . Но как бы то ни было, я не использую сборку Microsoft.VisualBasic, и я бы не стал добавлять ненужную зависимость в мой проект.

Во-вторых, в статье показано, как вывести существующий экземпляр приложения на передний план, когда пользователь пытается запустить другой экземпляр. Это очень приятное замечание, которое не рассматриваются в других решениях Mutex, описанных здесь.


UPDATE

По состоянию на 01.08.2014 статья, на которую я ссылался выше, все еще активна, но блог некоторое время не обновлялся. Это заставляет меня беспокоиться о том, что в конечном итоге оно может исчезнуть, а вместе с ним и предлагаемое решение. Я воспроизводю содержание статьи здесь для потомков. Слова принадлежат исключительно владельцу блога на Sanity Free Coding .

Сегодня я хотел провести рефакторинг кода, запрещающего мое приложение. от запуска нескольких экземпляров себя.

Ранее мне приходилось использовать System.Diagnostics.Process для поиска экземпляр моего myapp.exe в списке процессов. Пока это работает, это влечет за собой много накладных расходов, и я хотел что-то чище.

Зная, что я мог бы использовать мьютекс для этого (но никогда не делал этого раньше) Я решил сократить свой код и упростить свою жизнь.

В классе основного приложения я создал статический файл с именем Mutex :

static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    ...
}

Наличие именованного мьютекса позволяет нам синхронизировать стек несколько потоков и процессов, что просто магия, которую я ищу для.

Mutex.WaitOne имеет перегрузку, которая определяет количество времени для нас ждать. Поскольку мы на самом деле не хотим синхронизировать наш код (больше просто проверьте, используется ли он в настоящее время), мы используем перегрузку с два параметра: Mutex.WaitOne (Тайм-аут промежутка времени, bool exitContext) . Подождите, один возвращает истину, если он может войти, и ложь, если это не так. В этом случае мы вообще не хотим ждать; Если наш мьютекс используется, пропустить его и двигаться дальше, поэтому мы переходим в TimeSpan.Zero (ждать 0 миллисекунды), и установите для exitContext значение true, чтобы мы могли выйти из контекст синхронизации, прежде чем мы попытаемся установить для него блокировку. С помощью это, мы обертываем наш код Application.Run внутри чего-то вроде этого:

static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    static void Main() {
        if(mutex.WaitOne(TimeSpan.Zero, true)) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
            mutex.ReleaseMutex();
        } else {
            MessageBox.Show("only one instance at a time");
        }
    }
}

Итак, если наше приложение работает, WaitOne вернет false, и мы получим окно сообщения.

Вместо того, чтобы показывать окно сообщения, я решил использовать небольшой Win32 для уведомить мой работающий экземпляр, что кто-то забыл, что это уже работает (сводя себя к вершине всех других окон). к Для достижения этой цели я использовал PostMessage для трансляции каждого сообщения окно (пользовательское сообщение было зарегистрировано с RegisterWindowMessage моим запущенным приложением, что означает, что только мое приложение знает, что это так), то мой второй экземпляр выходит. Экземпляр работающего приложения получит это уведомление и обработает его. Для этого я переопределил WndProc в моей основной форме и прослушал мой кастом уведомление. Когда я получил это уведомление, я установил форму Свойство TopMost имеет значение true, чтобы поднять его на вершину.

Вот что я закончил:

  • Program.cs
static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    static void Main() {
        if(mutex.WaitOne(TimeSpan.Zero, true)) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
            mutex.ReleaseMutex();
        } else {
            // send our Win32 message to make the currently running instance
            // jump on top of all the other windows
            NativeMethods.PostMessage(
                (IntPtr)NativeMethods.HWND_BROADCAST,
                NativeMethods.WM_SHOWME,
                IntPtr.Zero,
                IntPtr.Zero);
        }
    }
}
  • NativeMethods.cs
// this class just wraps some Win32 stuff that we're going to use
internal class NativeMethods
{
    public const int HWND_BROADCAST = 0xffff;
    public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
    [DllImport("user32")]
    public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
    [DllImport("user32")]
    public static extern int RegisterWindowMessage(string message);
}
  • Form1.cs (частичная лицевая сторона)
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    protected override void WndProc(ref Message m)
    {
        if(m.Msg == NativeMethods.WM_SHOWME) {
            ShowMe();
        }
        base.WndProc(ref m);
    }
    private void ShowMe()
    {
        if(WindowState == FormWindowState.Minimized) {
            WindowState = FormWindowState.Normal;
        }
        // get our current "TopMost" value (ours will always be false though)
        bool top = TopMost;
        // make our form jump to the top of everything
        TopMost = true;
        // set it back to whatever it was
        TopMost = top;
    }
}
100 голосов
/ 21 августа 2008

Вы можете использовать класс Mutex, но вскоре вы обнаружите, что вам нужно будет реализовать код для передачи аргументов и тому подобное. Что ж, я научился уловке при программировании в WinForms, когда прочитал книгу Криса Села . Этот трюк использует логику, которая уже доступна нам в рамках. Я не знаю о вас, но когда я узнаю о вещах, которые я могу повторно использовать в рамках, это обычно - путь, который я выбираю вместо того, чтобы заново изобретать колесо. Если, конечно, он не делает все, что я хочу.

Когда я попал в WPF, я нашел способ использовать тот же код, но в приложении WPF. Это решение должно отвечать вашим потребностям исходя из вашего вопроса.

Сначала нам нужно создать класс нашего приложения. В этом классе мы переопределим событие OnStartup и создадим метод с именем Activate, который будет использоваться позже.

public class SingleInstanceApplication : System.Windows.Application
{
    protected override void OnStartup(System.Windows.StartupEventArgs e)
    {
        // Call the OnStartup event on our base class
        base.OnStartup(e);

        // Create our MainWindow and show it
        MainWindow window = new MainWindow();
        window.Show();
    }

    public void Activate()
    {
        // Reactivate the main window
        MainWindow.Activate();
    }
}

Во-вторых, нам нужно создать класс, который сможет управлять нашими экземплярами. Прежде чем мы пройдем через это, мы собираемся повторно использовать некоторый код в сборке Microsoft.VisualBasic. Поскольку в этом примере я использую C #, мне пришлось сделать ссылку на сборку. Если вы используете VB.NET, вам не нужно ничего делать. Класс, который мы собираемся использовать, - это WindowsFormsApplicationBase, который наследует наш менеджер экземпляров и использует свойства и события для обработки одного экземпляра.

public class SingleInstanceManager : Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase
{
    private SingleInstanceApplication _application;
    private System.Collections.ObjectModel.ReadOnlyCollection<string> _commandLine;

    public SingleInstanceManager()
    {
        IsSingleInstance = true;
    }

    protected override bool OnStartup(Microsoft.VisualBasic.ApplicationServices.StartupEventArgs eventArgs)
    {
        // First time _application is launched
        _commandLine = eventArgs.CommandLine;
        _application = new SingleInstanceApplication();
        _application.Run();
        return false;
    }

    protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
    {
        // Subsequent launches
        base.OnStartupNextInstance(eventArgs);
        _commandLine = eventArgs.CommandLine;
        _application.Activate();
    }
}

По сути, мы используем биты VB для обнаружения одного экземпляра и обработки соответствующим образом. OnStartup будет запущен при загрузке первого экземпляра. OnStartupNextInstance запускается при повторном запуске приложения. Как видите, я могу получить то, что было передано в командной строке через аргументы события. Я установил значение в поле экземпляра. Вы можете проанализировать командную строку здесь или передать ее приложению через конструктор и вызов метода Activate.

В-третьих, пришло время создать нашу EntryPoint. Вместо того, чтобы обновлять приложение, как вы это обычно делаете, мы собираемся воспользоваться нашим SingleInstanceManager.

public class EntryPoint
{
    [STAThread]
    public static void Main(string[] args)
    {
        SingleInstanceManager manager = new SingleInstanceManager();
        manager.Run(args);
    }
}

Что ж, я надеюсь, что вы сможете следить за всем, сможете использовать эту реализацию и сделать ее своей собственной.

81 голосов
/ 21 августа 2008

С здесь .

Распространенное использование межпроцессного Mutex - обеспечить одновременный запуск только одного экземпляра программы. Вот как это делается:

class OneAtATimePlease {

  // Use a name unique to the application (eg include your company URL)
  static Mutex mutex = new Mutex (false, "oreilly.com OneAtATimeDemo");

  static void Main()
  {
    // Wait 5 seconds if contended – in case another instance
    // of the program is in the process of shutting down.
    if (!mutex.WaitOne(TimeSpan.FromSeconds (5), false))
    {
        Console.WriteLine("Another instance of the app is running. Bye!");
        return;
    }

    try
    {    
        Console.WriteLine("Running - press Enter to exit");
        Console.ReadLine();
    }
    finally
    {
        mutex.ReleaseMutex();
    }    
  }    
}

Хорошей особенностью Mutex является то, что если приложение завершается без предварительного вызова ReleaseMutex, CLR автоматически освобождает Mutex.

55 голосов
/ 28 сентября 2010

MSDN на самом деле имеет пример приложения для C # и VB, чтобы сделать именно это: http://msdn.microsoft.com/en-us/library/ms771662(v=VS.90).aspx

Самая распространенная и надежная техника для разработки одного экземпляра обнаружение заключается в использовании Microsoft .NET Инфраструктура удаленного взаимодействия (System.Remoting). Microsoft .NET Framework (версия 2.0) включает в себя тип, WindowsFormsApplicationBase, который инкапсулирует необходимый функциональность удаленного взаимодействия. Включить этот тип в приложение WPF, Тип должен быть выведен из него, и быть используется в качестве прокладки между приложением метод статической точки входа, Main и Приложение WPF тип. Шим определяет, когда приложение запускается, и когда последующие запуски попытался, и дает контроль над WPF Тип приложения, чтобы определить, как обработать запуски.

  • Для C # люди просто делают глубокий вдох и забывают обо всем «Я не хочу включать VisualBasic DLL». Из-за этого и того, что Скотт Хансельман говорит , и того факта, что это в значительной степени самое чистое решение проблемы, оно разработано людьми, которые знают гораздо больше о среде, чем вы .
  • С точки зрения удобства использования, факт заключается в том, что если ваш пользователь загружает приложение, и оно уже открыто, и вы сообщаете ему сообщение об ошибке, подобное 'Another instance of the app is running. Bye', тогда он не будет очень счастливым пользователем. Вы просто ДОЛЖНЫ (в приложении с графическим интерфейсом) переключиться на это приложение и передать предоставленные аргументы - или, если параметры командной строки не имеют значения, вы должны открыть приложение, которое могло быть свернуто.

Фреймворк уже поддерживает это - просто какой-то идиот по имени DLL Microsoft.VisualBasic, и его не поместили в Microsoft.ApplicationUtils или что-то в этом роде. Преодолей это - или открой Отражатель.

Совет: если вы используете этот подход в точности так, как есть, и у вас уже есть App.xaml с ресурсами и т. Д., Вам нужно взглянуть на это тоже .

22 голосов
/ 25 августа 2011

Этот код должен идти к основному методу. Посмотрите здесь для получения дополнительной информации о методе main в WPF.

[DllImport("user32.dll")]
private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);

private const int SW_SHOWMAXIMIZED = 3;

static void Main() 
{
    Process currentProcess = Process.GetCurrentProcess();
    var runningProcess = (from process in Process.GetProcesses()
                          where
                            process.Id != currentProcess.Id &&
                            process.ProcessName.Equals(
                              currentProcess.ProcessName,
                              StringComparison.Ordinal)
                          select process).FirstOrDefault();
    if (runningProcess != null)
    {
        ShowWindow(runningProcess.MainWindowHandle, SW_SHOWMAXIMIZED);
       return; 
    }
}

Метод 2

static void Main()
{
    string procName = Process.GetCurrentProcess().ProcessName;
    // get the list of all processes by that name

    Process[] processes=Process.GetProcessesByName(procName);

    if (processes.Length > 1)
    {
        MessageBox.Show(procName + " already running");  
        return;
    } 
    else
    {
        // Application.Run(...);
    }
}

Примечание: Приведенные выше методы предполагают, что ваш процесс / приложение имеет уникальное имя. Потому что он использует имя процесса, чтобы найти, если какие-либо существующие процессоры. Таким образом, если ваше приложение имеет очень распространенное имя (например, «Блокнот»), вышеуказанный подход не будет работать.

17 голосов
/ 19 февраля 2010

Ну, у меня есть одноразовый класс для этого, который легко работает для большинства случаев использования:

Используйте это так:

static void Main()
{
    using (SingleInstanceMutex sim = new SingleInstanceMutex())
    {
        if (sim.IsOtherInstanceRunning)
        {
            Application.Exit();
        }

        // Initialize program here.
    }
}

Вот оно:

/// <summary>
/// Represents a <see cref="SingleInstanceMutex"/> class.
/// </summary>
public partial class SingleInstanceMutex : IDisposable
{
    #region Fields

    /// <summary>
    /// Indicator whether another instance of this application is running or not.
    /// </summary>
    private bool isNoOtherInstanceRunning;

    /// <summary>
    /// The <see cref="Mutex"/> used to ask for other instances of this application.
    /// </summary>
    private Mutex singleInstanceMutex = null;

    /// <summary>
    /// An indicator whether this object is beeing actively disposed or not.
    /// </summary>
    private bool disposed;

    #endregion

    #region Constructor

    /// <summary>
    /// Initializes a new instance of the <see cref="SingleInstanceMutex"/> class.
    /// </summary>
    public SingleInstanceMutex()
    {
        this.singleInstanceMutex = new Mutex(true, Assembly.GetCallingAssembly().FullName, out this.isNoOtherInstanceRunning);
    }

    #endregion

    #region Properties

    /// <summary>
    /// Gets an indicator whether another instance of the application is running or not.
    /// </summary>
    public bool IsOtherInstanceRunning
    {
        get
        {
            return !this.isNoOtherInstanceRunning;
        }
    }

    #endregion

    #region Methods

    /// <summary>
    /// Closes the <see cref="SingleInstanceMutex"/>.
    /// </summary>
    public void Close()
    {
        this.ThrowIfDisposed();
        this.singleInstanceMutex.Close();
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            /* Release unmanaged ressources */

            if (disposing)
            {
                /* Release managed ressources */
                this.Close();
            }

            this.disposed = true;
        }
    }

    /// <summary>
    /// Throws an exception if something is tried to be done with an already disposed object.
    /// </summary>
    /// <remarks>
    /// All public methods of the class must first call this.
    /// </remarks>
    public void ThrowIfDisposed()
    {
        if (this.disposed)
        {
            throw new ObjectDisposedException(this.GetType().Name);
        }
    }

    #endregion
}
14 голосов
/ 28 мая 2010

Новое приложение, которое использует Mutex и IPC, а также передает любые аргументы командной строки в работающий экземпляр: Приложение единственного экземпляра WPF .

10 голосов
/ 13 мая 2013

Код C # .NET Single Instance Application , который является ссылкой для отмеченного ответа, является отличным началом.

Однако я обнаружил, что он не очень хорошо справляется со случаями, когда уже существующий экземпляр имеет модальное диалоговое окно, независимо от того, является ли это диалоговое окно управляемым (например, другой формой, такой как поле about), или неуправляемым. (например, OpenFileDialog даже при использовании стандартного класса .NET). С исходным кодом активируется основная форма, но модальная остается неактивной, что выглядит странно, плюс пользователь должен щелкнуть по ней, чтобы продолжить использование приложения.

Итак, я создал служебный класс SingleInstance для автоматической обработки всего этого для приложений Winforms и WPF.

Winforms

1) измените класс Program следующим образом:

static class Program
{
    public static readonly SingleInstance Singleton = new SingleInstance(typeof(Program).FullName);

    [STAThread]
    static void Main(string[] args)
    {
        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        Singleton.RunFirstInstance(() =>
        {
            SingleInstanceMain(args);
        });
    }

    public static void SingleInstanceMain(string[] args)
    {
        // standard code that was in Main now goes here
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}

2) изменить класс главного окна следующим образом:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    protected override void WndProc(ref Message m)
    {
        // if needed, the singleton will restore this window
        Program.Singleton.OnWndProc(this, m, true);

        // TODO: handle specific messages here if needed
        base.WndProc(ref m);
    }
}

WPF:

1) измените страницу приложения следующим образом (и убедитесь, что вы установили действие ее сборки на страницу, чтобы можно было переопределить метод Main):

public partial class App : Application
{
    public static readonly SingleInstance Singleton = new SingleInstance(typeof(App).FullName);

    [STAThread]
    public static void Main(string[] args)
    {
        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        Singleton.RunFirstInstance(() =>
        {
            SingleInstanceMain(args);
        });
    }

    public static void SingleInstanceMain(string[] args)
    {
        // standard code that was in Main now goes here
        App app = new App();
        app.InitializeComponent();
        app.Run();
    }
}

2) изменить класс главного окна следующим образом:

public partial class MainWindow : Window
{
    private HwndSource _source;

    public MainWindow()
    {
        InitializeComponent();
    }

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);
        _source = (HwndSource)PresentationSource.FromVisual(this);
        _source.AddHook(HwndSourceHook);
    }

    protected virtual IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        // if needed, the singleton will restore this window
        App.Singleton.OnWndProc(hwnd, msg, wParam, lParam, true, true);

        // TODO: handle other specific message
        return IntPtr.Zero;
    }

А вот служебный класс:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;

namespace SingleInstanceUtilities
{
    public sealed class SingleInstance
    {
        private const int HWND_BROADCAST = 0xFFFF;

        [DllImport("user32.dll")]
        private static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);

        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        private static extern int RegisterWindowMessage(string message);

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

        public SingleInstance(string uniqueName)
        {
            if (uniqueName == null)
                throw new ArgumentNullException("uniqueName");

            Mutex = new Mutex(true, uniqueName);
            Message = RegisterWindowMessage("WM_" + uniqueName);
        }

        public Mutex Mutex { get; private set; }
        public int Message { get; private set; }

        public void RunFirstInstance(Action action)
        {
            RunFirstInstance(action, IntPtr.Zero, IntPtr.Zero);
        }

        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        public void RunFirstInstance(Action action, IntPtr wParam, IntPtr lParam)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            if (WaitForMutext(wParam, lParam))
            {
                try
                {
                    action();
                }
                finally
                {
                    ReleaseMutex();
                }
            }
        }

        public static void ActivateWindow(IntPtr hwnd)
        {
            if (hwnd == IntPtr.Zero)
                return;

            FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(hwnd));
        }

        public void OnWndProc(IntPtr hwnd, int m, IntPtr wParam, IntPtr lParam, bool restorePlacement, bool activate)
        {
            if (m == Message)
            {
                if (restorePlacement)
                {
                    WindowPlacement placement = WindowPlacement.GetPlacement(hwnd, false);
                    if (placement.IsValid && placement.IsMinimized)
                    {
                        const int SW_SHOWNORMAL = 1;
                        placement.ShowCmd = SW_SHOWNORMAL;
                        placement.SetPlacement(hwnd);
                    }
                }

                if (activate)
                {
                    SetForegroundWindow(hwnd);
                    FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(hwnd));
                }
            }
        }

#if WINFORMS // define this for Winforms apps
        public void OnWndProc(System.Windows.Forms.Form form, int m, IntPtr wParam, IntPtr lParam, bool activate)
        {
            if (form == null)
                throw new ArgumentNullException("form");

            if (m == Message)
            {
                if (activate)
                {
                    if (form.WindowState == System.Windows.Forms.FormWindowState.Minimized)
                    {
                        form.WindowState = System.Windows.Forms.FormWindowState.Normal;
                    }

                    form.Activate();
                    FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(form.Handle));
                }
            }
        }

        public void OnWndProc(System.Windows.Forms.Form form, System.Windows.Forms.Message m, bool activate)
        {
            if (form == null)
                throw new ArgumentNullException("form");

            OnWndProc(form, m.Msg, m.WParam, m.LParam, activate);
        }
#endif

        public void ReleaseMutex()
        {
            Mutex.ReleaseMutex();
        }

        public bool WaitForMutext(bool force, IntPtr wParam, IntPtr lParam)
        {
            bool b = PrivateWaitForMutext(force);
            if (!b)
            {
                PostMessage((IntPtr)HWND_BROADCAST, Message, wParam, lParam);
            }
            return b;
        }

        public bool WaitForMutext(IntPtr wParam, IntPtr lParam)
        {
            return WaitForMutext(false, wParam, lParam);
        }

        private bool PrivateWaitForMutext(bool force)
        {
            if (force)
                return true;

            try
            {
                return Mutex.WaitOne(TimeSpan.Zero, true);
            }
            catch (AbandonedMutexException)
            {
                return true;
            }
        }
    }

    // NOTE: don't add any field or public get/set property, as this must exactly map to Windows' WINDOWPLACEMENT structure
    [StructLayout(LayoutKind.Sequential)]
    public struct WindowPlacement
    {
        public int Length { get; set; }
        public int Flags { get; set; }
        public int ShowCmd { get; set; }
        public int MinPositionX { get; set; }
        public int MinPositionY { get; set; }
        public int MaxPositionX { get; set; }
        public int MaxPositionY { get; set; }
        public int NormalPositionLeft { get; set; }
        public int NormalPositionTop { get; set; }
        public int NormalPositionRight { get; set; }
        public int NormalPositionBottom { get; set; }

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool SetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool GetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);

        private const int SW_SHOWMINIMIZED = 2;

        public bool IsMinimized
        {
            get
            {
                return ShowCmd == SW_SHOWMINIMIZED;
            }
        }

        public bool IsValid
        {
            get
            {
                return Length == Marshal.SizeOf(typeof(WindowPlacement));
            }
        }

        public void SetPlacement(IntPtr windowHandle)
        {
            SetWindowPlacement(windowHandle, ref this);
        }

        public static WindowPlacement GetPlacement(IntPtr windowHandle, bool throwOnError)
        {
            WindowPlacement placement = new WindowPlacement();
            if (windowHandle == IntPtr.Zero)
                return placement;

            placement.Length = Marshal.SizeOf(typeof(WindowPlacement));
            if (!GetWindowPlacement(windowHandle, ref placement))
            {
                if (throwOnError)
                    throw new Win32Exception(Marshal.GetLastWin32Error());

                return new WindowPlacement();
            }
            return placement;
        }
    }

    public static class FormUtilities
    {
        [DllImport("user32.dll")]
        private static extern IntPtr GetWindow(IntPtr hWnd, int uCmd);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr SetActiveWindow(IntPtr hWnd);

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

        [DllImport("kernel32.dll")]
        public static extern int GetCurrentThreadId();

        private delegate bool EnumChildrenCallback(IntPtr hwnd, IntPtr lParam);

        [DllImport("user32.dll")]
        private static extern bool EnumThreadWindows(int dwThreadId, EnumChildrenCallback lpEnumFunc, IntPtr lParam);

        private class ModalWindowUtil
        {
            private const int GW_OWNER = 4;
            private int _maxOwnershipLevel;
            private IntPtr _maxOwnershipHandle;

            private bool EnumChildren(IntPtr hwnd, IntPtr lParam)
            {
                int level = 1;
                if (IsWindowVisible(hwnd) && IsOwned(lParam, hwnd, ref level))
                {
                    if (level > _maxOwnershipLevel)
                    {
                        _maxOwnershipHandle = hwnd;
                        _maxOwnershipLevel = level;
                    }
                }
                return true;
            }

            private static bool IsOwned(IntPtr owner, IntPtr hwnd, ref int level)
            {
                IntPtr o = GetWindow(hwnd, GW_OWNER);
                if (o == IntPtr.Zero)
                    return false;

                if (o == owner)
                    return true;

                level++;
                return IsOwned(owner, o, ref level);
            }

            public static void ActivateWindow(IntPtr hwnd)
            {
                if (hwnd != IntPtr.Zero)
                {
                    SetActiveWindow(hwnd);
                }
            }

            public static IntPtr GetModalWindow(IntPtr owner)
            {
                ModalWindowUtil util = new ModalWindowUtil();
                EnumThreadWindows(GetCurrentThreadId(), util.EnumChildren, owner);
                return util._maxOwnershipHandle; // may be IntPtr.Zero
            }
        }

        public static void ActivateWindow(IntPtr hwnd)
        {
            ModalWindowUtil.ActivateWindow(hwnd);
        }

        public static IntPtr GetModalWindow(IntPtr owner)
        {
            return ModalWindowUtil.GetModalWindow(owner);
        }
    }
}
9 голосов
/ 07 мая 2011

Вот пример, который позволяет вам иметь один экземпляр приложения. При загрузке любых новых экземпляров они передают свои аргументы основному экземпляру, который работает.

public partial class App : Application
{
    private static Mutex SingleMutex;
    public static uint MessageId;

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        IntPtr Result;
        IntPtr SendOk;
        Win32.COPYDATASTRUCT CopyData;
        string[] Args;
        IntPtr CopyDataMem;
        bool AllowMultipleInstances = false;

        Args = Environment.GetCommandLineArgs();

        // TODO: Replace {00000000-0000-0000-0000-000000000000} with your application's GUID
        MessageId   = Win32.RegisterWindowMessage("{00000000-0000-0000-0000-000000000000}");
        SingleMutex = new Mutex(false, "AppName");

        if ((AllowMultipleInstances) || (!AllowMultipleInstances && SingleMutex.WaitOne(1, true)))
        {
            new Main();
        }
        else if (Args.Length > 1)
        {
            foreach (Process Proc in Process.GetProcesses())
            {
                SendOk = Win32.SendMessageTimeout(Proc.MainWindowHandle, MessageId, IntPtr.Zero, IntPtr.Zero,
                    Win32.SendMessageTimeoutFlags.SMTO_BLOCK | Win32.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
                    2000, out Result);

                if (SendOk == IntPtr.Zero)
                    continue;
                if ((uint)Result != MessageId)
                    continue;

                CopyDataMem = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Win32.COPYDATASTRUCT)));

                CopyData.dwData = IntPtr.Zero;
                CopyData.cbData = Args[1].Length*2;
                CopyData.lpData = Marshal.StringToHGlobalUni(Args[1]);

                Marshal.StructureToPtr(CopyData, CopyDataMem, false);

                Win32.SendMessageTimeout(Proc.MainWindowHandle, Win32.WM_COPYDATA, IntPtr.Zero, CopyDataMem,
                    Win32.SendMessageTimeoutFlags.SMTO_BLOCK | Win32.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
                    5000, out Result);

                Marshal.FreeHGlobal(CopyData.lpData);
                Marshal.FreeHGlobal(CopyDataMem);
            }

            Shutdown(0);
        }
    }
}

public partial class Main : Window
{
    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        HwndSource Source;

        Source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
        Source.AddHook(new HwndSourceHook(Window_Proc));
    }

    private IntPtr Window_Proc(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam, ref bool Handled)
    {
        Win32.COPYDATASTRUCT CopyData;
        string Path;

        if (Msg == Win32.WM_COPYDATA)
        {
            CopyData = (Win32.COPYDATASTRUCT)Marshal.PtrToStructure(lParam, typeof(Win32.COPYDATASTRUCT));
            Path = Marshal.PtrToStringUni(CopyData.lpData, CopyData.cbData / 2);

            if (WindowState == WindowState.Minimized)
            {
                // Restore window from tray
            }

            // Do whatever we want with information

            Activate();
            Focus();
        }

        if (Msg == App.MessageId)
        {
            Handled = true;
            return new IntPtr(App.MessageId);
        }

        return IntPtr.Zero;
    }
}

public class Win32
{
    public const uint WM_COPYDATA = 0x004A;

    public struct COPYDATASTRUCT
    {
        public IntPtr dwData;
        public int    cbData;
        public IntPtr lpData;
    }

    [Flags]
    public enum SendMessageTimeoutFlags : uint
    {
        SMTO_NORMAL             = 0x0000,
        SMTO_BLOCK              = 0x0001,
        SMTO_ABORTIFHUNG        = 0x0002,
        SMTO_NOTIMEOUTIFNOTHUNG = 0x0008
    }

    [DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)]
    public static extern uint RegisterWindowMessage(string lpString);
    [DllImport("user32.dll")]
    public static extern IntPtr SendMessageTimeout(
        IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam,
        SendMessageTimeoutFlags fuFlags, uint uTimeout, out IntPtr lpdwResult);
}
8 голосов
/ 18 декабря 2009

Просто некоторые мысли: Существуют случаи, когда требуется, чтобы только один экземпляр приложения не был «хромым», как некоторые, по вашему мнению. Приложения баз данных и т. Д. На порядок усложняются, если разрешить нескольким экземплярам приложения для одного пользователя доступ к базе данных (вы знаете, все это обновляет все записи, которые открываются в нескольких экземплярах приложения для пользователей машина и тд). Во-первых, для «конфликта имен» не используйте удобочитаемое имя - используйте вместо него GUID или, еще лучше, GUID + читаемое имя. Возможности столкновения имен просто выпали из радара, и Mutex это не волнует. Как кто-то указал, DOS-атака будет отстойной, но если злоумышленник попытается получить имя мьютекса и включить его в свое приложение, вы все равно будете мишенью, и вам придется приложить НАМНОГО больше для защиты себя, чем просто возиться с именем мьютекса. Также, если использовать вариант: Новый Mutex (true, «некоторый GUID плюс имя», вне AIsFirstInstance), у вас уже есть индикатор того, является ли Mutex первым экземпляром.

...