こんにちは!現場でバリバリとWindows Formsアプリを作っていると、避けて通れないのが「画面の動的なレイアウト変更」ですよね。
「ユーザーがボタンを押した分だけ入力欄を増やしたい」「データの件数に合わせて行数が自動で増えるグリッドを作りたい」――そんな時、とりあえずコントロールの `Location` (座標)をコードでゴリゴリ計算して配置していませんか?
ちょっと待ってください。その方法だと、ウィンドウサイズを変えられた途端にテキストボックスが重なったり、はみ出したりして、大惨事になってしまいます。
今回は、Excelマクロの「AutoFit」や手動レイアウトの呪縛から抜け出し、Windows Formsの隠れた名機能「TableLayoutPanel」を使い倒して、「絶対に崩れない動的グリッドレイアウト」を構築する極意を伝授します。ここをクリアすれば、あなたのUI設計スキルは一気にプロの領域へ到達しますよ。ついてきてくださいね!
—
1. なぜ「TableLayoutPanel」なのか?(生半可な座標計算の捨て方)
初心者がやりがちな失敗の筆頭が、`For` ピーポーでループを回しながら `New Point(x, y)` でコントロールを配置していく手法です。これには以下の致命的なデメリットがあります。
1. 解像度やDPIが変わると一発で崩れる
2. ウィンドウをリサイズされたときに対応できない
3. 行の途中にコントロールを追加・削除した際のズレ修正地獄
これらを鮮やかに解決するのが、WebのCSS GridやHTMLテーブルの概念をWindows Formsに持ち込んだ `TableLayoutPanel` です。
Excelのワークシートのように「セル」の概念があり、そこにコントロールを放り込むだけで、自動的に整列・リサイズを行ってくれます。
—
2. 実践:動的に行を追加・削除する発注入力フォームを作る
それでは、実際にVB.NETを使って、「行追加」ボタンを押すと、商品名と数量の入力欄が綺麗に下へ増えていく動的グリッド画面を作ってみましょう。
以下のコードは、そのままフォームのコードビハインド(`Form1.vb`)に貼り付けて動かせる実用的なサンプルです。
Public Class Form1
‘ 行管理用のカウンタ
Private currentRow As Integer = 0
Private Sub Form1_Load(sender As Object, e As EventArgs, t As EventArgs) Handles MyBase.Load
‘ 1. テーブルレイアウトの初期設定
‘ 2列、初期行数1でスタート
TableLayoutPanel1.ColumnCount = 2
TableLayoutPanel1.RowCount = 1
‘ 列幅の割合設定 (1列目は幅70%、2列目は幅30%など)
TableLayoutPanel1.ColumnStyles.Clear()
TableLayoutPanel1.ColumnStyles.Add(New ColumnStyle(SizeType.Percent, 70.0F))
TableLayoutPanel1.ColumnStyles.Add(New ColumnStyle(SizeType.Percent, 30.0F))
‘ 初期行の高さを設定(AutoSizeにしておくと中身に合わせて伸縮します)
TableLayoutPanel1.RowStyles.Clear()
TableLayoutPanel1.RowStyles.Add(New RowStyle(SizeType.AutoSize))
‘ 最初の1行目をセットアップ
AddDataRow(“商品A”, “1”)
End Sub
‘ 【行追加ボタン】が押された時の処理
Private Sub btnAddRow_Click(sender As Object, e As EventArgs) Handles btnAddRow.Click
‘ 描画のチラつきを防ぐため、レイアウトロジックを一時停止
TableLayoutPanel1.SuspendLayout()
Try
‘ 1. テーブルの行数を1つ増やす
TableLayoutPanel1.RowCount += 1
‘ 2. 新しい行のスタイルを追加(AutoSizeを指定)
TableLayoutPanel1.RowStyles.Add(New RowStyle(SizeType.AutoSize))
‘ 3. コントロールを生成してセルに配置
AddDataRow(“”, “”)
Finally
‘ 4. 処理が終わったらレイアウトを再開
TableLayoutPanel1.ResumeLayout()
End Try
End Sub
‘ 行データを追加するヘルパーメソッド
Private Sub AddDataRow(productName As String, quantity As String)
‘ 商品名入力用のテキストボックス
Dim txtProduct As New TextBox()
txtProduct.Text = productName
txtProduct.Dock = DockStyle.Fill ‘ セルいっぱいに広げる
‘ 数量入力用の数値テキストボックス(簡易的にTextBoxを使用)
Dim numQuantity As New TextBox()
numQuantity.Text = quantity
numQuantity.Dock = DockStyle.Fill
‘ TableLayoutPanelの指定した行(currentRow)の、0列目と1列目に追加
TableLayoutPanel1.Controls.Add(txtProduct, 0, currentRow)
TableLayoutPanel1.Controls.Add(numQuantity, 1, currentRow)
‘ 次の行へインデックスを進める
currentRow += 1
End Sub
End Class
—
3. コードのキモ:ここがプロの技
上記のコードには、業務アプリを安定稼働させるための重要なテクニックが詰まっています。ポイントを解説しますね。
① `SuspendLayout()` と `ResumeLayout()` のペア
動的にコントロールを1つずつ追加・削除していくと、OSはその都度「画面を再描画しなきゃ!」と走り回ります。これが画面のチラつき(カクつき)やパフォーマンス低下の原因になります。
コントロールをごっそりいじる前には必ず `SuspendLayout()` で思考を停止させ、終わったら `ResumeLayout()` で一気に目を覚まさせる。 これ鉄則です。
② `Dock = DockStyle.Fill` の魔法
配置するテキストボックスなどのコントロールに対し、`Dock = DockStyle.Fill` を指定しています。これによって、ユーザーがウィンドウの幅を変えたり、親であるTableLayoutPanelの列幅が変わったりしたときに、コントロールがセルの大きさに合わせて自動的にビヨーンと伸縮してくれます。固定サイズで涙を飲む必要はもうありません。
③ `SizeType.AutoSize` による無限スクロール耐性
行のスタイルに `SizeType.AutoSize` を指定しています。これにより、行の中にあるコントロールの高さ(フォントサイズ変更など含む)に合わせて、行の高さが自動調整されます。
—
4. 陥りがちな罠:「あれ、レイアウトが崩れた?」を防ぐチェックリスト
TableLayoutPanelを使っていると、ふとした拍子にレイアウトが崩れることがあります。現場でよくあるトラブルシューティングを共有しておきます。
- 罠1:スクロールバーが出ない!
- 対策: 動的行追加でフォームの縦幅を超えた場合、TableLayoutPanelをそのまま置いているだけだと下にはみ出します。フォーム側に `AutoScroll = True` を設定するか、`Panel` コントロールの中に TableLayoutPanel を配置して Panel 側でスクロール制御を行ってください。
- 罠2:コントロールの幅がなぜか縮んでしまう
- 対策: コントロールの `Anchor` や `Dock` プロパティが適切に設定されていない可能性が高いです。基本は `Dock = DockStyle.Fill`、あるいは適切にマージン(`Margin`)を設定してセル内の余白を美しく保ちましょう。
—
まとめ:レイアウト地獄から解放されよう
今回は、TableLayoutPanelを使った動的グリッドレイアウトの極意について解説しました。
- 座標計算による手動レイアウトは今すぐ捨てる
- `TableLayoutPanel` の `ColumnCount` / `RowCount` と `RowStyles` をプログラムから制御する
- `SuspendLayout` / `ResumeLayout` でパフォーマンスと描画崩れをガードする
ここをしっかりと押さえておけば、どんなに複雑な入力フォームや動的明細画面を要求されても、涼しい顔してエレガントに実装できるようになります。
「ここをもっとこうしたい」「こんな応用はどう書くの?」という疑問があれば、いつでも先輩エンジニアに聞いてくださいね。あなたのWindows Formsライフが快適になることを応援しています!
