◆ 全部 Form 閉じたら終了がちょっとめんどうかも

WPF の起動時をカスタマイズする

この記事を書くときのメモに WinForms の情報もあったので書いておきます

メインフォーム開くまで

デフォルトではメインフォームを開くまではこんな風になってます
static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new Form1());
}

Application.Run にフォームのインスタンスを渡す形になってます
WPF のときみたいに自由に Show できるようにします
static class Program
{
    /// <summary>
    /// アプリケーションのメイン エントリ ポイントです。
    /// </summary>

    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new AppContext());
    }
}
public class AppContext : ApplicationContext
{
    public AppContext()
    {
        var startup_form = new Form1();
        startup_form.Show();
        this.MainForm = startup_form;
    }
}

AppContext というクラスを継承して実行時の処理を書きます
Application.Run には Form のインスタンスの代わりに AppContext のインスタンスを渡します

MainForm プロパティに Form を設定すれば その Form が閉じられるとアプリケーションが終了します
WPF みたいに全部閉じるまで終了したくないなら一手間必要です

1つ目の方法は Idle 状態になったら Form の数を数えて 0 なら終了させる方法
Application.Idle += (sender, e) =>
{
    if (Application.OpenForms.Count == 0)
    {
        Application.Exit();
    }
};

2つ目は Form のベースクラスを作って それぞれの Closed イベントで Form が自分で最後なら終了する方法
public class FormBase : Form
{
    protected override void OnClosed(EventArgs e)
    {
        base.OnClosed(e);
        if (Application.OpenForms.Count == 1)
        {
            Application.Exit();
        }
    }
}
こんなクラスを作って すべての Form がこれを継承すればおっけいです
Idle と違ってこまめに終了していいかチェックしなくて済みます

0 じゃなく 1 なのはこのときはまだ自分の分が残っているからです


どちらの場合も MainForm を設定しないように気をつけないと MainForm があればそれが閉じると終了してしまいます

多重起動禁止

これは WPF のと同じ方法で大丈夫です

グローバルエラーのハンドル

WinForms だとエントリポイントのクラスが static なのでインスタンスを作りません
なので WPF にあった app.DispatcherUnhandledException というものはありません

Application.Run の前に Application.ThreadException イベントにハンドラをセットしておきます
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

Application.ThreadException += (o, e) =>
{
    MessageBox.Show("Catch app unhandled exception.\n" + e.Exception.Message);
};

AppDomain.CurrentDomain.UnhandledException += (o, e) =>
{
    var unhandled = (Exception)e.ExceptionObject;
    MessageBox.Show("Catch appdomain unhandled exception.\n" + unhandled.Message);
};

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());

Application.ThreadException は UI スレッドのハンドルされなかった例外でイベントが起きます
WPF の DispatcherUnhandledException に代わるものです

全部の例外を受け取る AppDomain.CurrentDomain.UnhandledException は WinForms でも使えます

Application.ThreadException の場合は DispatcherUnhandledException で Handled を true にしたのと同じでこっちで受け取ると AppDomain の方にはイベントが行かなくなります

SetUnhandledExceptionMode

ThreadException ハンドラに送られるかは Application.SetUnhandledExceptionMode で変更できます
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
を設定すると ThreadException に送られて ThreadException で CatchException されます

すべて AppDomain で受け取るなど ThreadException で Catch したくないときは
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
を設定すれば ThreadException ハンドラは呼び出されなくなります

何も設定しないときは Automatic になっていて App.config の設定が反映されます
特に指定ないなら CatchException と同じで ThreadException ハンドラが呼び出されます

WPF と同じボタンで試すと
private void button1_Click(object sender, EventArgs e)
{
    throw new Exception("普通に例外");
}

private void button2_Click(object sender, EventArgs e)
{
    var thread = new Thread(new ThreadStart(() => {
        throw new Exception("別スレ例外");
    }));
    thread.Start();
}

private void button3_Click(object sender, EventArgs e)
{
    await Task.Run(() =>
    {
        throw new Exception("await task例外");
    });
}

private void button4_Click(object sender, EventArgs e)
{
    Task.Run(() =>
    {
        throw new Exception("task例外");
    });
}

結果です
ThreadExceptionAppDomain
button1×
button2×
button3×
button4××


ThreadException で受け取ると AppDomain の方には行かない違いだけであとは一緒です