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

アプリケーション開始時のフォームを非表示にする

ここでは、アプリケーション開始時はフォームを表示させずに、条件によって表示する方法を紹介します。なお、NotifyIconコンポーネントを使用してタスクトレイにアイコンを表示される場合などのように、フォームのインスタンスは作成してもフォームを表示させないでアプリケーションを開始する方法は「フォームを表示させずにトレイアイコンを表示する」で紹介します。

開始時にフォームを非表示する方法は、MSDNの「フォームが開始時に非表示になるように設定する」で紹介されています。このように、エントリポイントのMainメソッドでメインフォームを表示するかを判断し、条件が満たされた時のみメインフォームを表示するようにします。MSDNの例ではForm.ShowDialogメソッドでフォームを表示させていますが、通常はApplication.Runで表示させた方が良いでしょう。

メインフォームを表示させる前にメッセージボックスを表示して、Yesボタンがクリックされた時だけメインフォームを表示する例を紹介します。エントリポイントのMainメソッドを下のコードのように書き換えてください。エントリポイントを書き換える方法が分からないという方は、「アプリケーションのエントリポイントを自作する」をご覧ください。

VB.NET
コードを隠すコードを選択
<STAThread> _
Shared Sub Main()
    'Yesの時のみ、フォームを表示する
    If MessageBox.Show("フォームを表示しますか?", _
            "質問", MessageBoxButtons.YesNo) = _
                System.Windows.Forms.DialogResult.Yes Then
        '.NET Framework 1.0の時は下の1行を削除
        Application.EnableVisualStyles()
        '.NET Framework 1.1以下の時は下の1行を削除
        Application.SetCompatibleTextRenderingDefault(False)

        Application.Run(New Form1())
    End If
End Sub
C#
コードを隠すコードを選択
[STAThread]
public static void Main()
{
    //Yesの時のみ、フォームを表示する
    if (MessageBox.Show("フォームを表示しますか?", "質問",
        MessageBoxButtons.YesNo) == DialogResult.Yes)
    {
        //.NET Framework 1.0の時は下の1行を削除
        Application.EnableVisualStyles();
        //.NET Framework 1.1以下の時は下の1行を削除
        Application.SetCompatibleTextRenderingDefault(false);

        Application.Run(new Form1());
    }
}

以下は余談ですので、興味のある方のみお読みください。

Application.RunとForm.ShowDialogの違い

MSDNの「フォームが開始時に非表示になるように設定する」では、Application.Runメソッドでは無く、Form.ShowDialogメソッドでフォームを表示させていました。メインフォームをShowDialogメソッドで表示させたときと、Application.Runメソッドで表示させたときの違いは何でしょうか?

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

「Application.Run() はメッセージ ポンプを起動します。これは、特定のアプリケーションの動作に不可欠なものであり、アプリケーションの有効期間内の特定の時点(シャットダウン時など) におけるフォームの動作に影響を与える場合があります。」(このMSDNのページのコードにはApplication.Runが一切使われておらず、ShowDialogを使っているというのにこの説明では、明らかにおかしいですが。)

さらに、Application.Runメソッドについて「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では修正されたようです。)

STAThreadの意味は?

MSDNの「フォームが開始時に非表示になるように設定する」のコードには、MainメソッドにSTAThreadAttribute属性が付いていません。STAThreadAttribute属性は必要なのでしょうか?

STAThreadAttribute属性の意味については、MSDNの「STAThreadAttribute クラス」にあります。その説明は、「アプリケーションのCOMスレッドモデルがシングルスレッドアパートメント(STA: Single-Threaded Apartment)であることを示します」というものです。もしMainメソッドにSTAThreadAttribute属性を付けなかったならば、C#では、マルチスレッドアパートメント(MTA: multithreaded apartment)になります。ただしVB.NETでは、MainメソッドにSTAThreadAttribute属性を付けなくてもSTAになります。

STAとMTAについては、Wikipediaの「Component Object Model」の「COMのスレッド」に説明があります。簡単に言うと、STAは単一のスレッドでCOMを実行させるために必要で、それはWindowsのメッセージキューを利用して行われます。よってSTAでしか動作しないCOMはスレッドセーフではありませんが、MTAで動作するCOMは自分でスレッドの同期を取る必要があり、時にはスレッドをブロックします。

話を元に戻してMainメソッドにSTAThreadが必要かということで言うと、STAでしか動かないCOMを使用する場合は、必要ということになります(VB.NETの場合はデフォルトでSTAなので必要はありませんが、明示的に記述しておいた方がよいでしょう)。多くのCOM(特にユーザーインターフェース関係のCOM)はSTAでしか動作しないため、通常はCOMを使用する場合に必要になります。

自分でCOMを直接使っていないとしても、.NET Frameworkの一部の機能はCOMを使用しているため、そのような機能を使用する場合は、必ずSTAにする必要があります。STAでないと使用できない.NET Frameworkの機能には、以下のようなものがあります。(下記の「参考」にあるページ等を参考にしました。)

  1. ドラッグ&ドロップ機能
  2. クリップボード関係の機能
  3. OpenFileDialog、SaveFileDialogのようなFileDialogの派生クラスや、FolderBrowserDialogクラスによるダイアログの表示
  4. WebBrowserコントロール
  5. IMEの使用
  6. RichTextBoxコントロールの一部の機能(未確認)
  7. リフレクションを利用したメソッドの呼び出し(.NET Framework 1.0 のみ?)

実はVisual Studioのコード分析では、System.Windows.Forms名前空間を参照しており、MainメソッドにSTAThreadAttribute属性が付いていない場合、「Windows フォームのエントリ ポイントを STAThread でマークします」という警告が出ます。この警告の説明によると、STAThreadAttribute属性はWindowsフォームを使用するすべてのアプリケーションのエントリポイントに指定する必要があり、MTAはWindowsフォームでサポートされていないということです。

以上から、Windowsフォームアプリケーションでは、MainメソッドにSTAThreadAttribute属性が必要であると考えてよいでしょう。

  • 履歴:
  • 2010/4/15 .NET Framework 2.0の情報を追記。「STAThreadの意味は?」を大幅に書き換える。

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

  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。