ゆうなんとかさんの雑記帳的な。

Twitterで踊ったり音ゲーしたりしてるあの名前がよくわからない人が書いてるらしいよ。

WPFでコレクションの表示を高速化するオプションとか

ListViewにたくさんのデータを表示することを想定しています。

ItemsPanelにVertualizatingStackPanelを使う

見えていない部分はレンダリングしない(いい意味で)怠惰なStackPanelです。ただし、以下のことをすると仮想化が無効になりただのStackPanelと同じようになってしまいます。

  • 項目コンテナーが ItemsControl に直接的に追加される。 たとえば、アプリケーションで ListBoxItem オブジェクトが ListBox に明示的に追加される場合、ListBox は ListBoxItem オブジェクトを仮想化しません。
  • ItemsControl に含まれる項目コンテナーの種類が異なる。 たとえば、Separator オブジェクトを使用する Menu では、項目のリサイクルを実装できません。これは、Menu には、Separator 型のオブジェクトと MenuItem 型のオブジェクトが含まれるからです。
  • CanContentScroll を false に設定する。
  • IsVirtualizing を false に設定する。

要は

  • データはItemsSourceにバインドして使う
  • データの型はすべて揃える
  • スクロールできるようにする
  • 仮想化を有効にする

これらを守ればいいのです。

コンテナーのリサイクルを有効にする

既定ではビューアイテムは見えると生成されて見えなくなると破棄されます。しかし、リサイクルを有効にするとなるべく使いまわすので、ビューアイテムを生成・破棄するの分のコストが減ります。
有効にするにはVirtualizationModeプロパティをVirtualizationMode.Recyclingに設定します。

遅延スクロールを有効にする

これはスクロールしている間は更新をさぼり、スクロールが終わったらデータを更新するオプションです。
有効にするにはIsDeferredScrollingEnabledプロパティをtrueにします。

ItemsSourceにObservableCollectionを使う

ObservableCollectionクラスは、コレクションアイテムの追加・変更・移動・削除を感知するイベントを発行することができるコレクションクラスです。このため、全体を更新しなくて済むため、ただのListクラスやIEnumerableインターフェイスを実装したコレクションよりもデータの更新とレンダリングが早くなります。これが効果てきめんで超早くなります。

DependencyPropertyをバインドする

読み込み専用の値であってもバインディングの解決が早くなります。通常のプロパティをバインドするとき、WPFのデータバインディングはリフレクションを使うのですが、DependencyPropertyの場合は使いません。これがバインドの解決が早くなる理由。

バインドするオブジェクトを小さくする

並列してできるからなのでしょうか、たくさんのプロパティをもつひとつのオブジェクトをバインドするよりも、少ないプロパティのオブジェクトをたくさんバインドするほうが早く終わります。1000個くらいでも目に見えて違ってくるそうです。

以上のオプションを全部乗せしたXAMLがこちら

<ListView ScrollViewer.CanContentScroll="True"
          ScrollViewer.HorizontalScrollBarVisibility="Disabled"
          ScrollViewer.IsDeferredScrollingEnabled="True"
          ScrollViewer.PanningMode="VerticalOnly"
          ScrollViewer.VerticalScrollBarVisibility="Visible"
          VirtualizingPanel.IsVirtualizing="True"
          VirtualizingPanel.VirtualizationMode="Recycling"
          ItemsSource="{Binding Collection}">
    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel Orientation="Vertical"
                                    CanVerticallyScroll="True"/>
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>
</ListView>

ItemTemplateは極力シンプルにするといいらしいです。