ページ

2011年6月8日水曜日

[WP7] ListBox のデータの仮想化

Windows Phone 7 の ListBox はデフォルトでも VirtualizingStackPanel を使います。そのおかげで、見えている部分+α 程度の要素だけを生成します。
要するに、1万行ある ListBox でも最初にいきなり 1万行作るわけではなく、見えている部分の 10行ちょっと程度だけ作ることによって高速化してくれるわけです。
ただこれはビジュアル要素(TextBlock やら Border やらのこと)についてです。
データについては、1万行だったらあらかじめ 1万行分必要です。
それを、最初に 1万行分作らなくても済むようにしようというのが「データの仮想化」です。

Virtualizing Data in Windows Phone 7 Silverlight Applications
この記事によると IEnumerable では無く IList を継承したクラスを ItemsSource にセットすることで、必要になったデータのみを取得するようになってくれるようです。
ちなみに、必要なのは Count、IndexOf、this[] の get のみでそれ以外は NotImplementedException を投げるようにしとくだけでも構わないようです。

で、さっそく試してみみても常に全件取得されちゃってなぜかまったく仮想化されず悩みました。
ふと気付いて試してみてわかりましたが、ItemTemplate が無いと仮想化されないんですね。

<ListBox>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

このようにほとんど意味が無くても ItemTemplate は必須みたいです。

これで試してみると、最初にだいたい 100件程度分 this[] が呼び出されて、あとは ListBox をスクロールすると必要に応じて適当に this[] が呼び出されるという感じです。
もちろん、IList ではなく IList<T> を継承したクラスでも問題無く仮想化されました。

というか、IList と IEnumerable の両方を継承している場合はちゃんと仮想化されるようです(IList の方が優先されてる)。
なので、List<T>、ObservableCollection<T>、普通の配列など、いずれも大丈夫なんじゃないかと。
ダメなのは IEnumerable、もしくは、IEnumerable<T> しか無い場合です。
この場合は、GetEnumerator() が呼び出されて最初に全件取得されます。

というわけで、普通にコレクションクラスを使ってる時は自然とデータの仮想化も使ってることになりそうです。
IEnumerable しか無いっていうと、一番ありそうなのは LINQ 関連のことをやってる場合ですね。

public IEnumerable<Data> GetItemsSource()
{
    for (var i = 0; i < 10000; ++i)
    {
        yield return new Data(i);    // ←このデータを作るのが重い処理
    }
}

こんなメソッドがあって、これを ListBox.ItemsSource に渡してるとか、
もっと直接的に

this.ListBox.ItemsSource = from i in Enumerable.Range(0, 10000)
                           select new Data(i);    // ←このデータを作るのが重い処理

こんな風なことをしてると、1万個の Data クラスのインスタンスが出来上がるまで UI が固まってしまいます。(Data クラスの中身が string がいくつかあるだけとかなら、1万個くらい一瞬で出来上がっちゃうかな。そういう場合はほとんど気にする必要は無いと思いますが)
LINQ を使ってると、便利なんでついつい 「from なんちゃら~」 で済ませてしまおうとしちゃいがちなのでちょっと気をつけた方がいいかも。(と、自分に対して言っておくw)

0 件のコメント:

コメントを投稿