こんにちは! Windows FormsアプリケーションのUI設計、楽しんでいますか?
「画面のサイズを変えたときに、子コントロールを自分の思い通りにピタッと整列させたい……!」
業務アプリを作っていると、標準の `FlowLayoutPanel` や `TableLayoutPanel` ではどうしても表現できない、変態的……いや、非常に洗練された複雑なレイアウト要件に出くわすことがありますよね。
「Excelのマクロ記録の延長から抜け出して、本格的なデスクトップアプリのアーキテクトになりたい!」
そんなあなたへ。今回は、Windows Formsの心臓部である `LayoutEngine` を継承して、あなただけの「カスタムレイアウトエンジン」を自作する極限のテクニック を伝授します。
ここをクリアすれば、Visual Basic .NETのオブジェクト指向やイベント駆動UIの本質はバッチリですよ。さあ、一緒にプロの領域へ踏み込みましょう!
—
なぜ標準のパネルでは物足りないのか?
Windows Formsには、コントロールを並べるための優秀なコンテナが用意されています。
- `FlowLayoutPanel`: 横や縦に順次並べますが、折り返しの制御が少し大雑把です。
- `TableLayoutPanel`: Excelのシートのようなグリッド配置ですが、動的なセルの結合や複雑なウェイト配分を行おうとすると、途端にコードがスパゲッティ化します。
ここで私たちが目指すのは、「親コントロールのリサイズイベントに完全同期し、独自の数学的アルゴリズムで子コントロールの座標を高速かつ美しく計算・配置する専用エンジン」の自作です。
—
カスタムレイアウトの全体像(アーキテクチャ)
Windows Formsで独自のレイアウトを実現するには、以下の2つのクラスを連携させます。
1. レイアウトエンジン(`LayoutEngine` の継承): 整列の「計算ロジック」だけを担当する軽量なクラス。
2. カスタムパネル(`Panel` や `UserControl` の継承): そのエンジンを自身の頭脳として採用するコンテナコントロール。
役割を綺麗に分離(関心の分離)するのが、保守性の高いプロフェッショナルなコードの鉄則です。
—
実装コード:黄金比スパイラル風(?)カスタムパネル
今回は、親パネルの幅に合わせて子コントロールを「タイル状に美しく敷き詰めつつ、余白を自動均等配分する」という、少し高度なカスタムレイアウトエンジンをVB.NETで実装してみましょう。
Visual Studioを開き、新しいクラスファイルを `TileLayoutEngine.vb` という名前で作成してください。
Imports System.Windows.Forms
Imports System.Drawing
”’
”’
Public Class TileLayoutEngine
Inherits LayoutEngine
‘ レイアウト処理の本体(親コントロールのサイズが変わるたびに呼ばれます)
Public Overrides Function Layout(container As Object, e As LayoutEventArgs) As Boolean
‘ 1. コンテナ(親パネル)の安全なキャスト
Dim parent As Control = TryCast(container, Control)
If parent Is Nothing OrElse parent.Controls.Count = 0 Then
Return MyBase.Layout(container, e)
End If
‘ 2. パラメータの定義(タイルサイズとマージン)
Dim tileWidth As Integer = 120
Dim tileHeight As Integer = 80
Dim spacing As Integer = 10
Dim currentX As Integer = spacing
Dim currentY As Integer = spacing
Dim maxRowHeight As Integer = tileHeight
‘ 3. 子コントロールを順番に走査して座標を再計算
For Each ctrl As Control In parent.Controls
If Not ctrl.Visible Then Continue For
‘ 右端を超えたら次の行へ折り返す
If currentX + tileWidth + spacing > parent.ClientRectangle.Width Then
currentX = spacing
currentY += maxRowHeight + spacing
End If
‘ 子コントロールのサイズと位置を確定(ここでUIが再描画されます)
ctrl.Bounds = New Rectangle(currentX, currentY, tileWidth, tileHeight)
‘ 次の位置へX座標を進める
currentX += tileWidth + spacing
Next
‘ Trueを返すと「レイアウト処理が完了した」ことを親に伝えます
Return False
End Function
End Class
コードのここが重要!
- `Inherits LayoutEngine`: Windows Formsのレイアウトパイプラインに割り込むための魔法の呪文です。
- `ctrl.Bounds = …`: 子コントロールの位置(Location)と大きさ(Size)を一括で設定しています。ここがパフォーマンスのキモであり、無駄な再描画を発生させないプロの技です。
—
次に、このエンジンを搭載する「カスタムパネル」を作る
エンジンができたので、次はそれを搭載する器(コンテナ)を作ります。同じプロジェクト内に `TilePanel.vb` を作成してください。
Imports System.Windows.Forms
Imports System.Windows.Forms.Layout
”’
”’
Public Class TilePanel
Inherits Panel
‘ 自作したレイアウトエンジンのインスタンスを保持
Private ReadOnly _tileEngine As New TileLayoutEngine()
‘ LayoutEngineプロパティをオーバーライドして、自作エンジンをWindows Formsに教え込む
Public Overrides ReadOnly Property LayoutEngine As LayoutEngine
Get
Return _tileEngine
End Get
End Property
End Class
たったこれだけです!これで、VisualStudioのツールボックスに `TilePanel` が現れ、中にボタンやラベルを放り込むだけで、自動的に私たちが書いたアルゴリズムで整列するようになります。
—
現場でありがちな「陥りやすいエラー」と対策
初心者がこの領域に踏み込んだとき、9割の確率でハマる罠があります。
罠1:無限ループ地獄(StackOverflowException)
レイアウトロジックの中で、うっかり子コントロールの `Size` や `Location` を変えるだけでなく、親パネル自身の `Size` を変えてしまうコードを書くとどうなるでしょう?
→ 親が変わる = `Layout` メソッドが呼ばれる = 親のサイズをまた変える = 永遠に終わらない……(クラッシュ!)
対策: レイアウトエンジン内では、「親のサイズを変化させる操作」を絶対にしないこと。あくまで子コントロールの配置計算だけに徹します。
罠2:パフォーマンスの低下(カクつき)
子コントロールが100個、200個と増えたとき、リサイズするたびに画面が激しくチラついたり(フリッカ)、カクついたりすることがあります。
対策: カスタムパネルクラスのコンストラクタで、以下のダブルバッファリングを有効化する設定を入れておきましょう。
Public Sub New()
MyBase.New()
‘ 描画のチラつきを極限まで抑えるプロフェッショナル設定
Me.SetStyle(ControlStyles.OptimizedDoubleBuffer Or _
ControlStyles.AllPaintingInWmPaint Or _
ControlStyles.UserPaint, True)
Me.UpdateStyles()
End Sub
—
先輩エンジニアからのエール
お疲れ様でした! 今回は `LayoutEngine` を継承するという、Windows Formsの裏側の仕組みに直接アプローチする高度な解説を行いました。
「ただコントロールを配置する」だけのコードから、「コントロールの振る舞いそのものをデザインする」というエンジニア視点へのシフト。ここをクリアできたあなたは、もうマクロの記録や初心者レベルのプログラミングからは完全に卒業しています。
業務アプリのUIで「こんな風に動かしたいのに、標準じゃできない……」と諦めかける場面に出会ったら、ぜひ今回のカスタムレイアウトエンジンの話を思い出してください。あなたのアイデア次第で、Windows Formsの表現力は無限大に広がります。
それでは、また次の極限の世界でお会いしましょう。実装を楽しんでください!
