ごきげんよう市民!
今日はアプリケーションの二重起動の防止について学びます。
アプリケーションの二重起動の防止
あなたもご存じの通り、アプリケーションの不要な二重(多重)起動を防止しておくと、ユーザビリティが向上します。
「……あっ!?二つ起動してた。じゃあ一つ消して……うわ、設定値が初期値に上書きされちゃった!」なんて間抜けな事故を防げます(アプリが二重に起動していて、アプリ終了時に設定を保存する仕組みになっていたりすると、意図せずして上書きされてしまったりする場合がありますからね!)。
Mutexを使用した実装例
市民、あなたはMutexを使用することで、アプリ間を跨いでリソースを監視することができます。
Mutexとは、リソースへの排他的アクセスを提供するクラスです。
using System;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApp2
{
internal static class Program
{
private static Mutex mutex;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
bool createdNew;
mutex = new Mutex(true, "SampleApplication", out createdNew);
try
{
// 二重起動をチェック
if (!createdNew)
{
Console.WriteLine("アプリケーションは既に実行中です。");
return;
}
Application.Run(new Form1());
}
finally
{
// ミューテックスが取得されている場合にのみリリース
if (createdNew)
{
mutex.ReleaseMutex();
}
}
}
}
}
とても簡単です。
private static Mutex mutex;
このようにMutexを宣言し、それを取得したり、解放したりしているだけです。
おっと、Mutexを使用した場合は、ReleaseMutexを忘れないでください。
リソースを使い終わったら、元の位置に!
基本ですよね?
すべてのアプリに二重(多重)起動の防止が必要なわけではない
全てのアプリにおいて、二重(多重)起動を防止する必要はありません。
例えばペイントなんかは二重起動オーケーです。
別々の絵を描きたいときだってあるし、画像を見比べる必要があるときだってあるでしょう。
ペイント
一個しか起動しないはずで、二つ以上あるとややこしい場合は多重起動しません。
さて、市民。アプリケーションの二重起動を防止した後、……どうしましょうか?
「こういう時どういう実装にすればいいんだっけ?」と思いましたら、適当な(=程よい)アプリケーションをぱちぱちっと起動してみるのがいいかと思います。えーっと、そうですね……。このたとえが適切か分かりませんが、Steamとか。ううーん。
Microsoft Storeなんかどうです? 私は使ったことがありませんけどね。
Microsoft Store
- 何もしない。何も起きない(黙って、片方を起動しないだけで終わる)。
- 「アプリケーションが二重起動しています。終了します。」というメッセージボックスを出す。
- すでに起動しているアプリを前面に出す。
ほかにも方法があるかもしれませんが、私が思いつくのは3つです。1と2は簡単ですが、3は少し工夫が要ります。
すでに起動しているアプリを前面に出す
この3つ目は、簡単そうに見えて少々厄介です。
Win32 APIを使用する必要があるためです。
……ああ、怖がらなくてもいいですよ!
このように、APIを使用するための宣言をします。
SetForegroundWindowでウィンドウを前面に出します。
IsIconicで最小化を判定し、ShowWindowAsyncで前面表示させます。
[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);
SW_RESTOREはShowWindow 関数の定義値を参照してください。
「ウィンドウをアクティブにして表示」したいので、9です。マジックナンバーになってしまうので、定義しているのですね。
private const int SW_RESTORE = 9;
おっと、
using System.Runtime.InteropServices;
も忘れないでくださいね。
前面に表示するためのプロセスを取得する関数、GetPreviousProcessを用意しましょう。プロセス名からの検索になります。
public static Process GetPreviousProcess()
{
Process curProcess = Process.GetCurrentProcess();
string currentPath = curProcess.MainModule.FileName;
Process[] allProcesses = Process.GetProcessesByName(curProcess.ProcessName);
foreach (Process checkProcess in allProcesses)
{
if (checkProcess.Id != curProcess.Id)
{
string checkPath;
try
{
checkPath = checkProcess.MainModule.FileName;
}
catch (System.ComponentModel.Win32Exception)
{
// アクセス権限がない場合は無視
continue;
}
// プロセスのフルパス名を比較して同じアプリケーションか検証
if (String.Compare(checkPath, currentPath, true) == 0)
{
return checkProcess;
}
}
}
return null;
}
参考にしたのはこちらです(少し改変しました)。
@IT 多重起動禁止時に実行中のWindowsアプリケーションを最前面に表示するには?
FindWindow関数は不向き
FindoWindow関数というものもあります。
しかし、市民、このシチュエーションでは、FindWindow関数が使えない(使いづらい)のには注意してください。
FindWindowは、ウィンドウタイトル(またはクラス名)から取得する関数ですが、これはウィンドウのタイトルから検索しますので、同名のタイトル……たとえば、ファイルエクスプローラーでアプリケーションの場所を開いている場合なんかにも引っ掛かります。その場合、ファイルエクスプローラーが前面に表示されてしまいます。
実現はできますが、妙なところで引っかかるので、注意が必要です。
はじめてWinAPIを使用するときは見慣れなさに驚くかもしれません。これはWindowsの機能を使って、別のアプリケーションを触るための機構です。今回は自分のアプリケーションを起動させていますが、べつに、操作できるのは自分のアプリケーションに限りません。
例えば、あなたのアプリケーションのボタンを押したら、後ろに引っ込めておいた「火山の娘」が前面に来るようなソフトを作れるんです! わくわくしませんか?(そうでもない?)
すでに起動しているアプリを前面に出す場合のコード
全体としては、このようなものになります。
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApp2
{
internal static class Program
{
private static extern bool SetForegroundWindow(IntPtr hWnd);
[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);
private const int SW_RESTORE = 9; // 画面を元の大きさに戻す
private static Mutex mutex;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
bool createdNew;
mutex = new Mutex(true, "SampleApplication", out createdNew);
if (!createdNew)
{
Process previousProcess = GetPreviousProcess();
if (previousProcess != null)
{
// 既存のプロセスのメインウィンドウを前面に表示
IntPtr hWnd = previousProcess.MainWindowHandle;
if (hWnd != IntPtr.Zero)
{
// 最小化されている場合は元のサイズに戻す
if (IsIconic(hWnd))
{
ShowWindowAsync(hWnd, SW_RESTORE);
}
SetForegroundWindow(hWnd);
}
else
{
MessageBox.Show("既に実行中のウィンドウが見つかりませんでした。");
}
}
return;
}
Application.Run(new Form1());
}
public static Process GetPreviousProcess()
{
Process curProcess = Process.GetCurrentProcess();
string currentPath = curProcess.MainModule.FileName;
Process[] allProcesses = Process.GetProcessesByName(curProcess.ProcessName);
foreach (Process checkProcess in allProcesses)
{
if (checkProcess.Id != curProcess.Id)
{
string checkPath;
try
{
checkPath = checkProcess.MainModule.FileName;
}
catch (System.ComponentModel.Win32Exception)
{
// アクセス権限がない場合は無視
continue;
}
// プロセスのフルパス名を比較して同じアプリケーションか検証
if (String.Compare(checkPath, currentPath, true) == 0)
{
return checkProcess;
}
}
}
return null;
}
}
}
どうでしょうか、市民。さらなるユーザービリティの向上をはかってください。