2013年7月2日

Application.Exit() / Application.Restart() の注意事項 #1
フォームクローズのキャンセル

System.Windows.Forms 名前空間の Application.Exit() は、MSDNに記載してあるとおり、開いているフォームのいずれかの FormClosing イベントでクローズをキャンセル(FormClosingEventArgs.Cancel = true)した場合、アプリケーションは終了されない。
Application.Restart() に至っては、キャンセル時は、アプリケーションは終了されず、新たなアプリケーションが立ち上がる。まあ、MSDNに「主に ClickOnceアプリで使え」とあるので、Formアプリはあまり考慮されてないのかもしれない・・・。
※Application.Restart() の処理内容は簡単で、Formアプリの場合は Application.Exit() と同じ処理をした後、Process.Start()で自身の exe を起動するだけである。起動時のパラメーターも元のプロセスと同じ内容が使用される。(ClickOnceアプリや URL クリックで起動したアプリの場合は少々異なる。)

問題となるケース

フォームクローズをキャンセルする例として、下記のようにモードレスウィンドウの閉じる処理を、Hide() で行う場合がある。
この場合に、Application.Exit()/Restart() を使うと、終了しなかったり、アプリケーションが2つ立ち上がったりする。

public class ModelessForm : Form
{
    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        e.Cancel = true;
        Hide();
        base.OnFormClosing(e);
    }
// 以下省略
}

解決手段

おそらく、最も簡単で合理的な解決手段は、FormClosingEventArgs.CloseReason でクローズ理由を判定することである。
下記では、ユーザーによるUI操作で閉じられる場合のみ、キャンセルするようにしている。
そうすれば、Application.Exit()/Restart() の問題は起こらなくなる。(ユーザーのUI操作で、イベント内に記述したApplication.Exit()/Restart()が呼ばれても、CloseReason は UserClosing にはならない。)

public class ModelessForm : Form
{
    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        if( e.CloseReason == CloseReason.UserClosing ){
            e.Cancel = true;
            Hide();
        }
        base.OnFormClosing(e);
    }
// 以下省略
}

他の解決手段として、Application.Exit()/Restart() を自分で実装することもできる。
Application.OpenForms すべてに対して、Close() -> Dispose() するなど。
※再起動処理の挙動は、キャンセル無視の強制終了で新プロセス起動、または、キャンセル時は二重起動せず例外発生、など。

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.0