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