レイアウトに使用するPanelの中でも異色なVirtualizingPanel。とくにたくさんの項目をリストへ一度に読み込む場合、その性能が気になります。そこで、画面に表示される範囲の項目だけ処理を行うことで性能向上を計るコンポーネント……を作るための抽象クラスがVirtualizingPanelのようです。
これを実装したクラスはVirtualizingStackPanelの一つのみ。つまり、StackPanelで満足できない場合、自分で実装しなければいけません。
次の記事(英語)でVirtualizingPanelの実装について丁寧に説明されているようです(英語力がないので読めてません)。ここで紹介されているソースコード(VirtualizingTilePanelクラス)をありがたくダウンロードして、解読した備忘録を残します。
参照:Dan Crevier's Blog
はじめに、VirtualizingTilePanelクラスは、項目をWrapPanelのように配置するVirtualizingPanelの実装。ただし、水平方向のみをサポートし、項目のサイズは一定になる。
IScrollInfoの実装
VirtualizingPanelの継承と同時にIScrollInfoを実装し、スクロール周りの設定や情報を管理する。
・IScrollInfo.ExtentWidth/ExtentHeight: コンテンツの大きさ
・IScrollInfo.ViewportWidth/ViewportHeight: 表示範囲の大きさ、「表示範囲>=コンテンツ」なら、すべてのコンテンツが見えている状態
・IScrollInfo.HorizontalOffset/VerticalOffset: 表示範囲のオフセット
MeasureOverrideのオーバーライド
MeasureOverrideをオーバーライド。まずスクロール情報(ExtentとViewport)を更新する。次に、表示する範囲を求め、その範囲に含まれる項目だけ生成、InternalChildrenに追加する。このとき、範囲から漏れた生成済みの項目は解放している(※1)。
ArrangeOverrideのオーバーライド
ArrangeOverrideをオーバーライド。InternalChildrenに詰め込んだUIElementを配置する。
OnItemsChangedのオーバーライド
OnItemsChanged(親ItemsControlでItemsに変更があった)をオーバーライド。項目が削除された・置き換えられた・順序が変わった場合、変更のあった範囲の項目をInternalChildrenから解放する。
項目の生成
項目の生成にはItemContainerGeneratorを使う。ItemContainerGeneratorが項目の管理に使用するインデックス情報GeneratorPositionは以下の通り(分かりづらいよこれ)。
・GeneratorPosition.Index: 生成済み項目だけにふられる連番、未生成項目には直前の生成済み項目の番号が入る(無ければ「-1」)
・GeneratorPosition.Offset: 生成済み項目なら「0」、未生成項目なら直前の生成済み項目からのオフセット
※1 VirtualStackPanelのRecyclingモードでは、おそらく、解放せずにメモリに保持する(参照:VirtualizationMode列挙体、IRecyclingItemContainerGeneratorインターフェイス)。