例えばコントロールの大きさが変わった時のように、コントロールのレイアウト(子コントロールの配置)の変更が必要になるかもしれない時、Layoutイベントが発生し、レイアウト操作(LayoutEngine.Layoutメソッド)が実行されます。このようなレイアウトロジックが実行される可能性があるのは、コントロールの大きさや位置、Zオーダー、表示/非表示が変更された時の他、コントロールに子コントロールが追加、削除された時などです。
レイアウトロジックが実行される操作を連続して複数行うと、無駄なレイアウト操作が多発し、パフォーマンスが低下する恐れがあります。特にフォームや親コントロールに複数の子コントロールを追加する時は要注意です。
これを防ぐには、一時的にレイアウトロジックの実行を停止させます。コントロールのレイアウトロジックを一時的に中断するにはControl.SuspendLayoutメソッドを、再開するにはControl.ResumeLayoutメソッドを使用します。
Visual Studioのフォームデザイナが生成するコードでも、コントロールを作成、設定してフォームに追加する部分(InitializeComponentメソッド内)は、SuspendLayoutとResumeLayoutメソッドで囲まれています。
なおこれらのメソッドはそのコントロールだけに適用されるため、子コントロールのレイアウトロジックは中断しません。よって、必要があれば、フォームだけでなく、子コントロールを持つすべての親コントロールでSuspendLayoutとResumeLayoutメソッドを呼び出す必要があります。
レイアウトロジックが停止するとどうなるか実際に見てみましょう。フォームにButtonコントロール(Button1)を一つ配置して、DockプロパティをFillにしてください。そして、以下のようなコードをフォームのクラスに記述してください。ここで行っていることは、Button1をクリックするとフォームのレイアウトロジックを停止し(SuspendLayoutメソッドを呼び出し)、もう一度クリックすると再開する(ResumeLayoutメソッドを呼び出す)だけです。
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
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」はとてもよく似ていますが、違いもあります。これについては、「The SuperSecretSideEffect of ResumeLayout(false)」に説明があります。
実際に違いを確認してみましょう。フォームにButtonコントロールを3つ(Button1、Button2、Button3)配置して、以下のようなコードをフォームクラスに記述してください。
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
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ピクセル移動します)。
次にButton3をクリックしてください。今度もフォームの幅が100ピクセル広がります。しかし今度はButton1の位置が変わりません。そのため、Button1はフォームの右端から100ピクセル離れてしまいます。
ResumeLayout()の場合は、以前Button1がフォームの右端からどのくらい離れていたかを覚えており、その距離に基づいて新たな位置を計算し、移動させます。一方ResumeLayout(false)の場合は、以前の情報はリセットして、忘れてしまいます。そのため、Button1の位置は変わりません。
フォームデザイナが生成するコードでは、フォームに配置された親コントロールのResumeLayoutを呼び出す箇所は「ResumeLayout(false) + PerformLayout」となります。これは、それまでのコントロールの位置を無視してレイアウトしたいということだと思われます。このように、1からコントロールを配置する場合は、ResumeLayout(false)を使うことになるでしょう。