こんにちは。現場で鍛え抜かれたシステムアーキテクチャの知見を、あなたに共有できることを嬉しく思います。
「ListBoxに数千件のデータを放り込んだら、スクロールするたびに画面がカクついて使い物にならない……」
これは、VB.NETのWindows Forms開発で誰もが一度は通る「洗礼」です。なぜ遅いのか? それは、標準のListBoxが「高機能すぎる」からです。Windowsが裏側で「ここはどんなフォントだ? 文字の色は? 背景は?」と、毎回律儀に計算して描画しているからです。
今日は、この「標準描画」の呪縛を解き放ち、`DrawMode.OwnerDrawFixed` を使って、あなたのアプリケーションを爆速に変える極意を伝授します。
—
1. なぜ「OwnerDraw」が速いのか?
標準のListBoxは、一つ一つの項目を描画するたびに、OSの複雑な描画エンジンを呼び出します。アイテムが1,000個あれば、スクロールのたびにその計算が繰り返される。これが「カクつき」の正体です。
`OwnerDrawFixed`(固定サイズ・オーナードロー)に設定すると、Windowsからの「描画してくれ」という命令に対し、「はい、私が描きます。計算は全部パスしていいですよ」と横取りできる状態になります。描画の全責任を開発者が負う代わりに、無駄な処理をすべて削ぎ落とせるため、圧倒的なパフォーマンスを手に入れられるのです。
—
2. 実践:爆速ListBoxを実装する
まずは、ListBoxのプロパティを以下のように設定してください。
- DrawMode: `OwnerDrawFixed`
- ItemHeight: `20` (お好みの高さでOK)
次に、実際に描画を担当するイベントハンドラ(`DrawItem`)を記述します。
”’
”’ ここを軽量化するのがパフォーマンス向上の鍵です
”’
Private Sub ListBox1_DrawItem(sender As Object, e As DrawItemEventArgs) Handles ListBox1.DrawItem
‘ アイテムがない場合は何もしない
If e.Index < 0 Then Return
' 描画対象のListBoxを取得
Dim lb As ListBox = DirectCast(sender, ListBox)
' 背景色の決定(選択状態なら青系、そうでなければ白系)
Dim backColor As Color = If((e.State And DrawItemState.Selected) = DrawItemState.Selected,
SystemColors.Highlight, lb.BackColor)
Dim foreColor As Color = If((e.State And DrawItemState.Selected) = DrawItemState.Selected,
SystemColors.HighlightText, lb.ForeColor)
' 1. 背景を塗りつぶす(ここを削るとカクつく原因になります)
Using b As New SolidBrush(backColor)
e.Graphics.FillRectangle(b, e.Bounds)
End Using
' 2. 文字を描画する
Dim text As String = lb.Items(e.Index).ToString()
Using b As New SolidBrush(foreColor)
' 文字の配置を調整(上下中央寄せ)
Dim format As New StringFormat() With {.LineAlignment = StringAlignment.Center}
e.Graphics.DrawString(text, e.Font, b, e.Bounds, format)
End Using
' 3. フォーカス枠を描画(必要に応じて)
e.DrawFocusRectangle()
End Sub
---
3. ここで陥りやすい「罠」
コードを見て「おや?」と思った方は鋭いです。以下の2点に注意してください。
① `Using` ステートメントの重要性
`SolidBrush` などの描画リソースは、OSのメモリを直接消費します。`Using` を使わずに放置すると、メモリリークを起こし、数時間動かしただけでアプリが重くなります。「使い終わったら即破棄」は、VB.NETのメモリ管理の鉄則です。
② 大量更新時は `BeginUpdate` / `EndUpdate`
描画の高速化以前の話ですが、数千件のデータを追加する際は、必ず `ListBox1.BeginUpdate()` で描画を一時停止させ、追加完了後に `EndUpdate()` を呼んでください。これだけで、描画回数が劇的に減ります。
—
4. さらなる高みへ:CheckedListBoxの場合
`CheckedListBox` も同様に `DrawMode.OwnerDrawFixed` が使えます。チェックボックスの部分も自分で描画する必要がありますが、`ControlPaint.DrawCheckBox` という便利なメソッドが用意されています。
‘ DrawItem内にてチェックボックスを描画する例
ControlPaint.DrawCheckBox(e.Graphics, e.Bounds.X + 2, e.Bounds.Y + 2, 16, 16,
If(CheckedListBox1.GetItemChecked(e.Index),
ButtonState.Checked, ButtonState.Normal))
—
先輩エンジニアからのアドバイス
今回紹介した「オーナードロー」は、最初は少し難しく感じるかもしれません。「なぜ自分で描く必要があるんだ?」と思うでしょう。
しかし、この技術をマスターすると、「リストの中に画像を表示する」「特定の文字だけ赤くする」「データの重要度に応じて背景色を変える」といった、標準機能では不可能なリッチなUIを、爆速で実装できるようになります。
「標準機能でできないから諦める」のではなく、「仕組みを理解して自分でコントロールする」。これこそが、プロの自動化エンジニアへの第一歩です。
まずは、小さなリストから試してみてください。画面が吸い付くようにスクロールする快感を味わえば、きっとこの技術の虜になるはずですよ。応援しています!
