VB.NETの悪習「コントロール配列」を脱却せよ:動的UI生成の極意とイベント一元管理術
VB6時代、私たちは`Index`プロパティを振るだけで簡単にコントロール配列を作成できました。しかし、.NETの時代になり、その仕組みは過去のものとなりました。
多くの開発者が、無理やり「コントロール配列のようなもの」を実装しようとして、`Control.ControlCollection`を漁ったり、無意味な継承を繰り返してコードを肥大化させています。本稿では、VB.NETの流儀である`AddHandler`を駆使し、メモリを浪費せず、保守性の高い「動的UI管理」の極致を伝授します。
—
1. なぜ「コントロール配列」の模倣は危険なのか
VB.NETにおいて、単にコントロールをリストに詰め込むだけでは不十分です。
- メモリリークの温床: 動的生成したコントロールを破棄する際、`RemoveHandler`を怠れば、GC(ガベージコレクション)が回収不能なゾンビオブジェクトが発生します。
- スパゲッティコード: 各コントロールのイベントを個別に書くのは、保守性の観点から「罪」です。
私たちが目指すべきは、「イベントの集約」と「データバインディング的な考え方の導入」です。
—
2. 実践:動的生成とイベントの一元管理
ボタンを10個動的に生成し、クリックした際に「何番目のボタンか」を正確に判定する。この要件を最もスマートに解決するコードが以下です。
Imports System.Drawing
Public Class MainView
‘ 生成したコントロールを保持するリスト(管理用)
Private ReadOnly _dynamicButtons As New List(Of Button)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
‘ 10個のボタンを動的に配置
For i As Integer = 0 To 9
Dim btn As New Button With {
.Name = “btnDynamic_” & i,
.Text = “No.” & i,
.Location = New Point(10, 10 + (i 30)),
.Tag = i ‘ インデックスをTagに格納しておくのが定石
}
‘ ここが重要:AddHandlerでイベントを一つにまとめる
AddHandler btn.Click, AddressOf CommonButton_Click
Me.Controls.Add(btn)
_dynamicButtons.Add(btn)
Next
End Sub
”’
”’
Private Sub CommonButton_Click(sender As Object, e As EventArgs)
‘ senderを元の型にキャスト
Dim clickedBtn As Button = DirectCast(sender, Button)
‘ Tagプロパティから情報を取得
Dim index As Integer = CInt(clickedBtn.Tag)
MessageBox.Show($”ボタン {index} が押されました。処理を開始します。”)
‘ データベース連携等の重い処理は、この後に分離すべき
ExecuteBusinessLogic(index)
End Sub
‘ フォームクローズ時のメモリ解放はエンジニアの義務
Private Sub MainView_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
For Each btn In _dynamicButtons
RemoveHandler btn.Click, AddressOf CommonButton_Click
btn.Dispose()
Next
End Sub
End Class
—
3. 業務システムで「バグらせない」ための3つの鉄則
動的生成を取り入れる際、現場で必ず守るべき規律があります。
① インデックスよりも「Tag」や「ViewModel」を信じろ
`Controls`コレクションのインデックスに依存してはいけません。コントロールの追加・削除によって順序は容易に変わります。必ず`Tag`プロパティにIDを格納するか、あるいは生成したコントロールとデータモデルを対にした`Dictionary(Of String, DataModel)`を保持する設計にしてください。
② イベントの「重さ」を意識せよ
UIスレッドでデータベース接続や重い計算を行ってはいけません。`CommonButton_Click`内で行うのは「どのデータに対して処理を行うか」というIDの特定までです。実際の処理は`Task.Run`や非同期処理へ逃がし、UIのフリーズを回避してください。
③ 破棄(Dispose)の徹底
動的生成したコントロールは、Formのライフサイクルとは別に管理する必要があります。上記コードのように`List(Of T)`で参照を保持し、`FormClosing`で確実に`RemoveHandler`と`Dispose`を行ってください。これを怠ると、長時間稼働する業務端末で確実にメモリ不足を引き起こします。
—
エンジニアへの提言
VB.NETは「古い言語」と揶揄されることもありますが、それは使い手が言語の力を引き出せていないだけです。`AddHandler`による疎結合なイベント処理は、現代の疎結合アーキテクチャの先駆けとも言える手法です。
「とりあえず動く」コードから、「メンテナンスを考慮した堅牢なコード」へ。
あなたが書くその1行が、数年後の開発者を助けるか、あるいは苦しめるか。その責任感を持って、UIを制御してください。
次回の記事では、この動的UIとデータベースをMVVMパターンで結合する、一歩進んだ設計論について深掘りします。期待していてください。
