DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

Application.RunとForm.ShowDialogの違い

アプリケーションのメインフォームを表示する時、エントリポイント(詳しくは、「アプリケーションのエントリポイントを自作する」)でApplication.Runメソッドを呼び出すのが普通です。しかし、Form.ShowDialogメソッドを使っても表示することができます。ここでは両者の違いを考えます。

MSDNの「フォームが開始時に非表示になるように設定する」には、次のように書かれています。

「Application.Run() はメッセージ ポンプを起動します。これは、特定のアプリケーションの動作に不可欠なものであり、アプリケーションの有効期間内の特定の時点(シャットダウン時など) におけるフォームの動作に影響を与える場合があります。」

これを読むとApplication.Runでなければならないような気がしますが、不思議なことにこのページのコードにはApplication.Runが一切使われておらず、ShowDialogが使われています。

さらに、Application.RunメソッドについてMSDNの「Application.Run メソッド」では次のように書かれています。

「現在のスレッドで標準のアプリケーション メッセージ ループの実行を開始し、指定したフォームを表示します。通常、アプリケーションの main 関数でこのメソッドを呼び出し、アプリケーションのメイン ウィンドウに渡します。このメソッドは、 Closed イベントのmainForm パラメータにイベント ハンドラを追加します。イベント ハンドラは、 ExitThread を呼び出して、アプリケーションをクリーンアップします。」

以上の説明によれば、「Application.Run(new Form1())」というのは、「Form1を表示させ、Form1が閉じられるまでメッセージループをまわす」ということを意味しており、ウィンドウズアプリケーションには不可欠なものだということになります。

それではApplication.Runを使わずにForm.ShowDialogでメインフォームを表示した時は、メッセージループが回らないというのでしょうか?もちろんそうではありません。「Application.Run vs myForm.ShowDialog - microsoft.public.dotnet.languages.csharp」によると、Form.ShowDialogでメインフォームを表示させた時も、必要があると判断されれば、Application.Runと同様の処理が行われます。つまり、どちらでもあまり変わりません。両者の違いは、Application.Runではメインフォームが閉じられるとExitThreadメソッドが呼び出されることだということです。

補足:メッセージループとは、簡単に言うと、ループ処理によってイベントを監視するための仕組みです。詳しくは、Wikipediaの「メインループ」等をご覧ください。

このような両者の違いを具体的に調べるため、次のようなコードを書いてみました。ここではエントリポイントでForm1をモーダルで表示し、さらにForm1をクリックすることにより、Form2がモードレスで表示されるようにしています。さらに、Mainメソッドから抜ける直前と、Form2が閉じられる時にメッセージボックスが表示されるようにしています。Form1からForm2を表示させ、その後Form1を閉じるとどうなるでしょうか?

VB.NET
コードを隠すコードを選択
''' <summary>
''' アプリケーションのメイン エントリ ポイントです。
''' </summary>
<STAThread()> _
Shared Sub Main()
    'フォーム(Form1)のインスタンスを作成
    Dim f1 As New Form1
    'Clickイベントハンドラを追加
    AddHandler f1.Click, AddressOf f1_Click
    'フォーム(Form1)を表示
    f1.ShowDialog()
    'Application.Run(f1);

    '終了時にメッセージボックスを表示
    MessageBox.Show("Application End")
End Sub

Private Shared Sub f1_Click( _
    ByVal sender As Object, ByVal e As EventArgs)
    'Form2をモードレスで表示する
    Dim f2 As New Form2
    'Closedイベントハンドラを追加
    AddHandler f2.Closed, AddressOf f2_Closed
    'Disposedイベントハンドラを追加
    AddHandler f2.Disposed, AddressOf f2_Disposed
    f2.Show()
End Sub

Private Shared Sub f2_Closed( _
    ByVal sender As Object, ByVal e As EventArgs)
    'Form2が閉じられる時にメッセージボックスを表示する
    MessageBox.Show("Form2 Closed")
End Sub

Private Shared Sub f2_Disposed( _
    ByVal sender As Object, ByVal e As EventArgs)
    'Form2が破棄される時にメッセージボックスを表示する
    MessageBox.Show("Form2 Disposed")
End Sub
C#
コードを隠すコードを選択
/// <summary>
/// アプリケーションのメイン エントリ ポイントです。
/// </summary>
[STAThread]
static void Main() 
{
    //フォーム(Form1)のインスタンスを作成
    Form1 f1 = new Form1();
    //Clickイベントハンドラを追加
    f1.Click += new EventHandler(f1_Click);
    //フォーム(Form1)を表示
    f1.ShowDialog();
    //Application.Run(f1);

    //終了時にメッセージボックスを表示
    MessageBox.Show("Application End");
}

private static void f1_Click(object sender, EventArgs e)
{
    //Form2をモードレスで表示する
    Form2 f2 = new Form2();
    //Closedイベントハンドラを追加
    f2.Closed += new EventHandler(f2_Closed);
    //Disposedイベントハンドラを追加
    f2.Disposed += new EventHandler(f2_Disposed);
    f2.Show();
}

private static void f2_Closed(object sender, EventArgs e)
{
    //Form2が閉じられる時にメッセージボックスを表示する
    MessageBox.Show("Form2 Closed");
}

private static void f2_Disposed(object sender, EventArgs e)
{
    //Form2が破棄される時にメッセージボックスを表示する
    MessageBox.Show("Form2 Disposed");
}

上記のコードを実行させた場合(Form1をShowDialogメソッドで表示させた場合)、Form1からForm2を表示させ、その後Form1を閉じると、「Application End」というメッセージボックスのみが表示されます。つまり、Form1から開いたフォームのClosedやDisposedイベントは発生しません。

一方、Mainメソッドで、「f1.ShowDialog()」を「Application.Run(f1)」に書き換えると、表示されているForm2の数だけ「Form2 Disposed」と表示され、その後、「Application End」というメッセージボックスが表示されます。

つまり、ShowDialogでメインフォームを表示した時は、メインフォームを閉じると、それ以外のモードレスフォームのDisposedイベントが発生することなくアプリケーションが終了してしまいます。よって、ShowDialogでメインフォームを表示した時は、メインフォームを閉じる時に他のすべてのフォームを閉じる処理が必要になるかもしれません。

また、次のようにメインフォームのLoadイベントハンドラ内で別のフォームを表示させるときに、メインフォームをShowDialogメソッドで表示させると、おかしなことになります。

VB.NET
コードを隠すコードを選択
''' <summary>
''' アプリケーションのメイン エントリ ポイントです。
''' </summary>
<STAThread()> _
Shared Sub Main()
    'フォーム(Form1)のインスタンスを作成
    Dim f1 As New Form1
    'Loadイベントハンドラを追加
    AddHandler f1.Load, AddressOf f1_Load
    'フォーム(Form1)を表示
    f1.ShowDialog()
    'Application.Run(f1);
End Sub

Private Shared Sub f1_Load( _
    ByVal sender As Object, ByVal e As EventArgs)
    'Form1表示時にForm2をモードレスで表示する
    Dim f2 As New Form2
    f2.Show()
End Sub
C#
コードを隠すコードを選択
/// <summary>
/// アプリケーションのメイン エントリ ポイントです。
/// </summary>
[STAThread]
static void Main() 
{
    //フォーム(Form1)のインスタンスを作成
    Form1 f1 = new Form1();
    //Loadイベントハンドラを追加
    f1.Load += new EventHandler(f1_Load);
    //フォーム(Form1)を表示
    f1.ShowDialog();
    //Application.Run(f1);
}

private static void f1_Load(object sender, EventArgs e)
{
    //Form1表示時にForm2をモードレスで表示する
    Form2 f2 = new Form2();
    f2.Show();
}

上記のコードでは、Form1がモーダルフォームとして表示され、Form2の操作が一切できなくなります。Application.RunでForm1を表示させると、このような問題は起こりません。(この問題は.NET Framework 2.0では修正されたようです。)

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。