プログラム系統備忘録ブログ

記事中のコードは自己責任の下でご自由にどうぞ。

TreeViewのControlTemplateにScrollViewerを入れたらUIの仮想化が動かなかった話とその対処法

2015/04/13追記 Win7 64bitマシンで、プロジェクトの対象のフレームワークを.Net4.5として、マシンには.Net4.5.2をインストールした状態で、仮想化したTreeViewの要素をExpandしたときにスクロール位置が狂う(Expandした要素やその子供が範囲外に隠れる)現象を確認しています。 恐らく https://connect.microsoft.com/VisualStudio/feedback/details/783812/c-wpf-treeview-virtualized-issue-with-treeview-scrolling-on-item-expanded と同様の現象です。

2015/05/24追記 スクロールバーをマウス操作している時に、UIが完全にフリーズする現象が発生しました。https://connect.microsoft.com/VisualStudio/feedback/details/790241/mousescrooll-freezes-virtualizingpanel-if-isvirtualizingwhengrouping-and-scrollunit-pixel-set で言及されているように、`VirtualizingPanel.ScrollUnit="Item"`と指定したところ、今のところは再発していません。

背景

  1. WPFでDataGridとTreeViewを組み合わせたコントロールはどうすれば実現できるかググる。
  2. MSDNでTreeListViewという自作コントロールのサンプル解説を見つけたので触ってみる。
  3. スクロールバーがついていなかったので、見つけた記事を参考にScrollViewerをControlTemplateに組み込んでみる。(余談ですが、この内容だと幅が足りない時に縦スクロールバーが隠れてしまいます。※2015/04/09追記 ScrollViewer with non scrolling area (fixed header) の方法だとうまく行きました。)
  4. ふと試しに1ノードの下に1000の子要素を設定してExpandしてみると、数秒かかることが判明。UIの仮想化を試してみる。
  5. StyleやTemplateを未指定のTreeViewでは VirtualizingStackPanel.IsVirtualizing="True" を指定すると軽快な動作になる。けれど、2〜3で触っていたTreeListViewでは効果がなかった。

原因

Livetで始めるWPF(ざっくり)入門で、コントロールからControlTemplateを抜き出す方法を紹介されていたことを思い出し、TreeViewのTemplateを抜き出すと次の内容がありました(要所のみ抜粋)。

<ControlTemplate x:Key="TreeViewControlTemplate1" TargetType="{x:Type TreeView}">
    <Border ...>
        <ScrollViewer x:Name="_tv_scrollviewer_" ...>
            <ItemsPresenter />
        </ScrollViewer>
    </Border>
    <ControlTemplate.Triggers>
        ...
        <Trigger Property="VirtualizingPanel.IsVirtualizing" Value="True">
            <Setter Property="CanContentScroll" TargetName="_tv_scrollviewer_" Value="True" />
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

どう見ても大事なTriggerが抜け落ちていました。というわけでTriggerを追加(ScrollViewerを2つ設置しているので両方共設定)すると、UIの仮想化に成功し、Expandが一瞬で済むようになりました。

なお、MSDNのOptimizing Performance: Controlsのページを見ると

The following is a list of conditions that disable UI virtualization.
...
Setting CanContentScroll to false.

とあり、かつScrollViewer.CanContentScrollのデフォルト値はfalseでした。
教訓:ドキュメントをちゃんと読まないとバチが当たる。