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

2020/02/18

C# Prism

アイキャッチ

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

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

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

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

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

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

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

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のリソースは解放します。

if (semaphore != null)
{
    semaphore.Dispose();
}

※Null条件演算子を使えばシンプルに書けますが、あえて分かりやすい記述にしました。

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

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

自己紹介

自分の写真



新潟県のとある企業で働いてます。
【できる事】
電子回路設計
基板パターン設計
マイコンプログラム
C#(WinForms WPF)を使ったWindowsアプリケーション作成
PLCラダー
自動化装置アドバイザー
にほんブログ村 IT技術ブログ ソフトウェアへ

カテゴリ

このブログを検索

QooQ