TreeView 失去虚拟化与覆盖控制模板

TreeView loses virtualization with overridden control template

当我在 XAML 中有以下 TreeView 时(ItemsSource 通过代码隐藏设置为一长串列表):

<TreeView x:Name="Tree"
          VirtualizingPanel.IsVirtualizing="True"
          ScrollViewer.CanContentScroll="True">
    <!-- ItemTemplate and ItemContainerStyle ommitted for brevity -->
</TreeView>

虚拟化工作得很好。但是,当我尝试覆盖 TreeView 的模板以及内部 ScrollViewer 的模板时,虚拟化消失了。我的模板似乎与默认模板相同,除了我取出的彩色矩形外,这是重新定义模板的全部动机。

    <TreeView.Template>
        <ControlTemplate TargetType="{x:Type TreeView}">
            <ScrollViewer Focusable="False" 
                          CanContentScroll="True">
                <ScrollViewer.Template>
                    <ControlTemplate TargetType="{x:Type ScrollViewer}">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition />
                                <RowDefinition Height="auto" />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition />
                                <ColumnDefinition Width="auto" />
                            </Grid.ColumnDefinitions>

                            <ScrollContentPresenter Grid.Column="0" Grid.Row="0" />

                            <ScrollBar x:Name="PART_VerticalScrollBar" 
                                       Orientation="Vertical"
                                       Grid.Row="0" Grid.Column="1"
                                       Value="{TemplateBinding VerticalOffset}"
                                       Maximum="{TemplateBinding ScrollableHeight}"
                                       ViewportSize="{TemplateBinding ViewportHeight}"
                                       Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" />

                            <ScrollBar x:Name="PART_HorizontalScrollBar"
                                       Orientation="Horizontal"
                                       Grid.Row="1" Grid.Column="0"
                                       Value="{TemplateBinding HorizontalOffset}"
                                       Maximum="{TemplateBinding ScrollableWidth}"
                                       ViewportSize="{TemplateBinding ViewportWidth}"
                                       Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" />
                        </Grid>
                    </ControlTemplate>
                </ScrollViewer.Template>
                <ScrollViewer.Content>
                    <ItemsPresenter x:Name="ItemsPresenter" />
                </ScrollViewer.Content>
            </ScrollViewer>
        </ControlTemplate>

我通过在 TreeView 的资源中制作一个名为 {x:Static SystemColors.ControlBrushKey} 的透明 SolidColorBrush 暂时解决了这个问题,但我更想知道我做错了什么。

许多因素可能会破坏 UI 虚拟化,有时是您意想不到的:

  1. 将您的列表控件放在 ScrollViewer 中:ScrollViewer 提供了一个 window 它的子内容。问题是子内容被赋予无限的“虚拟” space。在此虚拟 space 中,ListBox 以全尺寸呈现自身,及其所有子项 展出的物品。作为副作用,每个项目都有自己的内存占用 列表框项目对象。每当您将 ListBox 放入容器中时,都会出现此问题 不会试图限制它的大小;例如,如果 您将其弹出到 StackPanel 而不是 Grid。
  2. 更改列表的控件模板并且无法使用 ItemsPresenter: ItemsPresenter 使用 ItemsPanelTemplate,它指定 虚拟化堆栈面板。如果你打破了这种关系,或者如果你改变了 ItemsPanelTemplate 你自己所以它不使用 VirtualizingStackPanel,你会失去 虚拟化功能。
  3. 不使用数据绑定:这应该是显而易见的,但是如果您以编程方式填充列表—— 例如,通过动态创建您需要的 ListBoxItem 对象——不 虚拟化将会发生。当然可以考虑使用自己的优化 策略,比如只创建那些你需要的对象,并且只在 需要的时候。

我发现了问题。在 ScrollViewer 模板中,ScrollContentPresenter 需要将 CanContentScroll 设置为其模板父项的值。

<ScrollContentPresenter Grid.Column="0" Grid.Row="0"
                        CanContentScroll="{TemplateBinding CanContentScroll}" />