【テクニカル・上級編】ComboBoxのインクリメンタルサーチ爆速化:Virtual Modeとカスタムキャッシュを用いた数万件マスターの絞り込み – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

数万件のマスターを瞬時に捌く:ComboBoxインクリメンタルサーチの「極限」最適化

業務アプリにおいて、ComboBoxに数万件の得意先マスターをバインドするのは「悪手」の代名詞だ。`DataSource`に数万件の`DataTable`をそのまま放り込み、`AutoCompleteMode`を有効にするなど、現代のメモリ管理においては自殺行為に等しい。文字を打つたびにUIスレッドがロックされ、ユーザーのストレスはピークに達する。

今日は、イベント駆動UIの限界を超え、数万件のマスターを「爆速」で絞り込むための、アーキテクトとしての回答を提示する。

—

1. なぜ標準のComboBoxではいけないのか?

標準の`ComboBox`は、アイテム数が数千を超えたあたりから、レンダリングと検索アルゴリズムが線形探索の罠に陥る。特に`.NET`の`AutoComplete`は、内部的に文字列比較を繰り返すため、キー入力のたびにガベージコレクション(GC)が頻発する。

我々が目指すべきは、「UIスレッドの解放」と「メモリの定数化」だ。

2. アーキテクチャの核心:Virtual Modeの擬似実装

ComboBoxには公式の`Virtual Mode`が存在しない。そのため、以下の手法を組み合わせる。

1. データソースの分離: `DataTable`をUIに持たせず、メモリ上のインデックスリスト(`List`)として保持する。
2. 遅延評価(Lazy Evaluation): キー入力後、数百ミリ秒のデバウンス(Debounce)を挟む。
3. 表示用キャッシュの限定: UIに表示するのは、絞り込まれた結果の「先頭100件」のみとする。

実装コード:爆速絞り込みエンジンの骨子

Imports System.Threading

Public Class FastComboBoxManager
‘ 数万件のマスターデータ(実体)
Private ReadOnly _masterData As List(Of Customer)
‘ 絞り込み後の表示用リスト
Private _filteredList As New List(Of Customer)

‘ デバウンス用のタイマー
Private _debounceTimer As System.Windows.Forms.Timer

Public Sub New(masterData As List(Of Customer))
_masterData = masterData
InitializeTimer()
End Sub

Private Sub InitializeTimer()
_debounceTimer = New System.Windows.Forms.Timer()
_debounceTimer.Interval = 200 ‘ 200msの遅延で入力を確定させる
AddHandler _debounceTimer.Tick, AddressOf OnDebounceTick
End Sub

‘ テキスト変更時のイベントハンドラ
Public Sub HandleTextChanged(targetComboBox As ComboBox)
_debounceTimer.Stop()
_debounceTimer.Start()
End Sub

Private Sub OnDebounceTick(sender As Object, e As EventArgs)
_debounceTimer.Stop()
‘ ここでLINQを使って絞り込み。数万件でも数ミリ秒で完了する
Dim query = targetComboBox.Text
_filteredList = _masterData.Where(Function(c) c.Name.Contains(query)) _
.Take(100) _
.ToList()

‘ UI更新を最小限に抑える
UpdateComboBox(targetComboBox)
End Sub
End Class

—

3. Windows APIで「カクつき」を物理的に消す

ComboBoxの`BeginUpdate`と`EndUpdate`を呼び出すだけでは不十分な場合がある。OSレベルで再描画を完全に停止させ、リストの更新を強制的に同期させるために、`SendMessage`によるメッセージの封印が有効だ。

Private Const WM_SETREDRAW As Integer = &HB


Private Shared Function SendMessage(hWnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
End Function

‘ 更新前に呼び出す
SendMessage(comboBox.Handle, WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero)

‘ … データの詰め替え処理 …

‘ 更新後に呼び出す
SendMessage(comboBox.Handle, WM_SETREDRAW, New IntPtr(1), IntPtr.Zero)
comboBox.Refresh()

この手法は、Windows Formsの描画サイクルに割り込みをかけるため、大量のアイテムを挿入する際でも画面のチラつきを皆無にできる。

—

4. メモリ管理とレガシーの呪縛

VB.NETのアプリケーションで数万件のオブジェクトを扱う際、最も恐ろしいのは「メモリリーク」ではなく「メモリの断片化」だ。

  • 明示的な解放: `DataTable`や`List`を再生成する際は、必ず`Clear()`を呼び出し、`GC.Collect()`を安易に呼ぶのではなく、`IDisposable`を実装したラッパーを介してリソースを管理せよ。
  • データ構造の選択: `string`の比較はコストが高い。もし可能であれば、IDやコードをバイナリ検索可能な配列(`Int32`のソート済みリスト)として保持し、名前の検索はサブセットで行うのが、伝説的なアーキテクトが辿り着く「正解」だ。

結論

ComboBoxのインクリメンタルサーチは、単なるUIパーツのプロパティ設定ではない。「データ検索エンジン」と「描画パイプライン」の最適化である。

フレームワークが提供する便利な機能に依存してはいけない。数万件のマスターを扱いたければ、泥臭いメモリ管理と、Windows APIの深淵を覗く覚悟が必要だ。この実装を導入すれば、どんなに巨大なマスターデータを持つシステムであっても、ユーザーは「爆速」という言葉以外の感想を抱くことはないだろう。

エンジニアよ、フレームワークの檻から抜け出し、CPUとメモリを支配せよ。

タイトルとURLをコピーしました