なべひろBlog

プログラミングをメインに仕事に関するアレコレを発信しています。

Prismを使ったWPFアプリケーションで多重起動を抑制する

多重起動が問題になるケースはごくまれですが、利用者が気づかず複数開いたウィンドウで「ソフトを終了したのに起動している!」といったプチパニックを起こさないためにも多重起動は対処した方がいいと思います。

多重起動が不具合だと勘違いされ呼ばれる方も面倒ですし、このような処理を省くのは利用者を考えない制作者の怠慢でしょう。

WinFormsやPrismを使わないWPFの多重起動は検索すれば色々出ますがPrismの環境下での多重起動は検索してもヒットしなかったのでちょっと書いてみます。

但し、一部「本当にこれでいいのか?」と思う所がありますので、ご存じの方おられましたらご指導お願い致します。

編集するのは「App.xaml」と「App.xaml.cs」で、それ以外は触りません。

まずは「App.xml」ですが、1行追加します。

これはアプリケーション終了時の処理です。

<prism:PrismApplication x:Class="MultipleStartup.App"
    既存のコードがいろいろあって
    Exit="Application_Exit"> ←これが追加される

Appxaml.csの内容を分割して紹介すると

まずはWin32APIを使用するおまじないと多重起動をチェックするSemaphoreです。

// 外部プロセスのメイン・ウィンドウを起動するためのWin32 API
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
private static extern bool IsIconic(IntPtr hWnd);
// ShowWindowAsync関数のパラメータに渡す定義値(画面を元の大きさに戻す)
private const int SW_RESTORE = 9;
Semaphore semaphore = null;

そしてCreateShellの内容です

semaphore = new Semaphore(1, 1, Assembly.GetExecutingAssembly().GetName().Name, out bool createdNew);
// まだアプリが起動してなければ
if (createdNew)
{
    return Container.Resolve<MainWindow>();
}
// 既にアプリが起動していればそのアプリを前面に出す
else
{
    foreach (var p in Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName))
    {
        // 自分自身のプロセスIDは無視する
        if (p.Id != Process.GetCurrentProcess().Id)
        {
            // プロセスのフルパス名を比較して同じアプリケーションか検証
            if (p.MainModule.FileName == Process.GetCurrentProcess().MainModule.FileName)
            {
                // メイン・ウィンドウが最小化されていれば元に戻す
                if (IsIconic(p.MainWindowHandle))
                {
                    ShowWindowAsync(p.MainWindowHandle, SW_RESTORE);
                }
                // メイン・ウィンドウを最前面に表示する
                SetForegroundWindow(p.MainWindowHandle);
            }
        }
    }
    Shutdown();
    return null;
}

Semaphoreは名前付きとしてSemaphoreが作成されたか否かを4番目の引数の値で判断します。

また、既に起動中なのに最小化してたりすると利用者は「あれ?起動しない?」と勘違いする可能性もあるので、ウィンドウが最小化されている場合は元に戻し、さらに他のウィンドウに隠れてしまい見落とす可能性もあるので最前面に表示させます。

最後にApplication_ExitでSemaphoreをDisposeして次にアプリを起動する時にはきちんと起動できるようSemaphoreのリソースは解放します。

private void Application_Exit(object sender, ExitEventArgs e)
{
    semaphore?.Dispose();
}

私がイマイチ自信がない部分はCreateShellで既にアプリが起動中な場合は戻り値をnullとしている部分です。

実験した結果は問題ありませんが...

サンプルプログラムはこちら

Prism関連記事

MVVMなWPFアプリケーションでバーコードリーダーの情報をキャッチする。

PrismのIDialogServiceを試してみる

MVVMなWPFアプリケーションでドラッグ&ドロップをしてみる

Prism使ったWPFでLivetCask.Messagingを使ってみる

Prismで複数のページを持ったアプリを作ってみる

PrismのUserControlを動的に配置する