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

コントロールのレイアウトを一時中断して、パフォーマンスを向上させる

例えばコントロールの大きさが変わった時のように、コントロールのレイアウト(子コントロールの配置)の変更が必要になるかもしれない時、Layoutイベントが発生し、レイアウト操作(LayoutEngine.Layoutメソッド)が実行されます。このようなレイアウトロジックが実行される可能性があるのは、コントロールの大きさや位置、Zオーダー、表示/非表示が変更された時の他、コントロールに子コントロールが追加、削除された時などです。

レイアウトロジックが実行される操作を連続して複数行うと、無駄なレイアウト操作が多発し、パフォーマンスが低下する恐れがあります。特にフォームや親コントロールに複数の子コントロールを追加する時は要注意です。

これを防ぐには、一時的にレイアウトロジックの実行を停止させます。コントロールのレイアウトロジックを一時的に中断するにはControl.SuspendLayoutメソッドを、再開するにはControl.ResumeLayoutメソッドを使用します。

Visual Studioのフォームデザイナが生成するコードでも、コントロールを作成、設定してフォームに追加する部分(InitializeComponentメソッド内)は、SuspendLayoutとResumeLayoutメソッドで囲まれています。

なおこれらのメソッドはそのコントロールだけに適用されるため、子コントロールのレイアウトロジックは中断しません。よって、必要があれば、フォームだけでなく、子コントロールを持つすべての親コントロールでSuspendLayoutとResumeLayoutメソッドを呼び出す必要があります。

具体例

レイアウトロジックが停止するとどうなるか実際に見てみましょう。フォームにButtonコントロール(Button1)を一つ配置して、DockプロパティをFillにしてください。そして、以下のようなコードをフォームのクラスに記述してください。ここで行っていることは、Button1をクリックするとフォームのレイアウトロジックを停止し(SuspendLayoutメソッドを呼び出し)、もう一度クリックすると再開する(ResumeLayoutメソッドを呼び出す)だけです。

VB.NET
コードを隠すコードを選択
Private isLayoutSuspended As Boolean = False

'Button1のClickイベントハンドラ
Private Sub Button1_Click(sender As Object, e As EventArgs) _
    Handles Button1.Click

    If Not isLayoutSuspended Then
        'レイアウトロジックを停止する
        Me.SuspendLayout()
    Else
        'レイアウトロジックを再開する
        Me.ResumeLayout()
    End If
    isLayoutSuspended = Not isLayoutSuspended
End Sub
C#
コードを隠すコードを選択
private bool isLayoutSuspended = false;

//Button1のClickイベントハンドラ
private void Button1_Click(object sender, EventArgs e)
{
    if(!isLayoutSuspended)
    {
        //レイアウトロジックを停止する
        this.SuspendLayout();
    }
    else
    {
        //レイアウトロジックを再開する
        this.ResumeLayout();
    }
    isLayoutSuspended = !isLayoutSuspended;
}

このプログラムを実行させて、フォームの大きさを変えてみてください。すると、フォームの大きさに応じてButtonコントロールの大きさも変化します(フォームのLayoutイベントが発生します)。しかしButtonをクリックしてレイアウトロジックを停止させてからフォームの大きさを変えてみると、Buttonの大きさは変化しなくなります(フォームのLayoutイベントも発生しなくなります)。もう一度ButtonをクリックしてResumeLayoutメソッドを呼び出すと、Buttonはフォームいっぱいに広がります(フォームのLayoutイベントが発生します)。そして以前と同じように、フォームの大きさを変えるとButtonの大きさも変化するようになります。

保留中のレイアウトを実行させない

上記のように引数なしでResumeLayoutメソッドを呼び出すと、保留中のレイアウト操作がすぐに実行されます。保留中のレイアウトを実行させたくないならば、引数にFalseを指定します。上記の例で「ResumeLayout()」を「ResumeLayout(false)」にすると、Button1をクリックした直後にButton1の大きさが変わることはなくなります(フォームのLayoutイベントが発生しません)。しかし、その後フォームの大きさを変えればButton1の大きさも変わります(フォームのLayoutイベントが発生します)。

レイアウトロジックをすぐに実行するメソッドとしてControl.PerformLayoutメソッドがありますので、「ResumeLayout()」と「ResumeLayout(false)の直後でPerformLayout()」はほぼ同じ結果になります。

「ResumeLayout()」と「ResumeLayout(false) + PerformLayout」の違い

「ResumeLayout()」と「ResumeLayout(false) + PerformLayout」はとてもよく似ていますが、違いもあります。これについては、「The SuperSecretSideEffect of ResumeLayout(false)」に説明があります。

実際に違いを確認してみましょう。フォームにButtonコントロールを3つ(Button1、Button2、Button3)配置して、以下のようなコードをフォームクラスに記述してください。

VB.NET
コードを隠すコードを選択
Private Sub Button1_Click(sender As Object, e As EventArgs) _
    Handles Button1.Click

    'Button1をフォームの右端にぴったり付けて固定する
    Button1.Left = Me.ClientSize.Width - Button1.Width
    Button1.Anchor = AnchorStyles.Right Or AnchorStyles.Top
End Sub

Private Sub Button2_Click(sender As Object, e As EventArgs) _
    Handles Button2.Click

    'レイアウトを中断して、フォームの幅を広げる
    Me.SuspendLayout()
    Me.Width += 100
    Me.ResumeLayout()

    'Button1はフォームの右端に
End Sub

Private Sub Button3_Click(sender As Object, e As EventArgs) _
    Handles Button3.Click

    'レイアウトを中断して、フォームの幅を広げる
    Me.SuspendLayout()
    Me.Width += 100
    Me.ResumeLayout(False)
    Me.PerformLayout()

    'Button1はフォームの右端から+100の位置に
End Sub
C#
コードを隠すコードを選択
private void Button1_Click(object sender, EventArgs e)
{
    //Button1をフォームの右端にぴったり付けて固定する
    Button1.Left = this.ClientSize.Width - Button1.Width;
    Button1.Anchor = AnchorStyles.Right | AnchorStyles.Top;
}

private void Button2_Click(object sender, EventArgs e)
{
    //レイアウトを中断して、フォームの幅を広げる
    this.SuspendLayout();
    this.Width += 100;
    this.ResumeLayout();

    //Button1はフォームの右端に
}

private void Button3_Click(object sender, EventArgs e)
{
    //レイアウトを中断して、フォームの幅を広げる
    this.SuspendLayout();
    this.Width += 100;
    this.ResumeLayout(false);
    this.PerformLayout();

    //Button1はフォームの右端から+100の位置に
}

まずButton1をクリックしてください。するとButton1はフォームの右端に移動します。さらにAnchorプロパティをRightにしていますので、フォームの大きさを変更してもButto1はフォームの右端にぴったりくっついた状態になります。

次にButton2をクリックしてください。すると、フォームの幅が100ピクセル広がります。この時、Button1は相変わらずフォームの右端にくっついています(つまり、Button1は右に100ピクセル移動します)。

「ResumeLayout()」の時

次にButton3をクリックしてください。今度もフォームの幅が100ピクセル広がります。しかし今度はButton1の位置が変わりません。そのため、Button1はフォームの右端から100ピクセル離れてしまいます。

「ResumeLayout(false)」の時

ResumeLayout()の場合は、以前Button1がフォームの右端からどのくらい離れていたかを覚えており、その距離に基づいて新たな位置を計算し、移動させます。一方ResumeLayout(false)の場合は、以前の情報はリセットして、忘れてしまいます。そのため、Button1の位置は変わりません。

フォームデザイナが生成するコードでは、フォームに配置された親コントロールのResumeLayoutを呼び出す箇所は「ResumeLayout(false) + PerformLayout」となります。これは、それまでのコントロールの位置を無視してレイアウトしたいということだと思われます。このように、1からコントロールを配置する場合は、ResumeLayout(false)を使うことになるでしょう。

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

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