TreeView loses virtualization with overridden control template

368 views Asked by At

When I have the following TreeView in XAML (ItemsSource is set via code-behind to a long list of lists of lists):

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

virtualization works just fine. However, when I tried overriding the TreeView's template, along with the inner ScrollViewer's template, virtualization disappeared. My template seems identical to the default, with the exception of a colored rectangle that I took out which was the entire motivation for redefining the template.

    <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>

I've temporarily gotten around this by just making a transparent SolidColorBrush named {x:Static SystemColors.ControlBrushKey} in the TreeView's resources, but I'd prefer to know what I've done incorrectly.

2

There are 2 answers

0
Brandon Hood On BEST ANSWER

I found the problem. In the ScrollViewer template, the ScrollContentPresenter needs to have CanContentScroll set to its templated parent's value.

<ScrollContentPresenter Grid.Column="0" Grid.Row="0"
                        CanContentScroll="{TemplateBinding CanContentScroll}" />
1
mohsen mousavi On

A number of factors can break UI virtualization, sometimes when you don’t expect it:

  1. Putting your list control in a ScrollViewer: The ScrollViewer provides a window onto its child content. The problem is that the child content is given unlimited “virtual” space. In this virtual space, the ListBox renders itself at full size, with all of its child items on display. As a side effect, each item gets its own memory-hogging ListBoxItem object. This problem occurs any time you place a ListBox in a container that doesn’t attempt to constrain its size; for example, the same problem crops up if you pop it into a StackPanel instead of a Grid.
  2. Changing the list’s control template and failing to use the ItemsPresenter: The ItemsPresenter uses the ItemsPanelTemplate, which specifies the VirtualizingStackPanel. If you break this relationship or if you change the ItemsPanelTemplate yourself so it doesn’t use a VirtualizingStackPanel, you’ll lose the virtualization feature.
  3. Not using data binding: It should be obvious, but if you fill a list programmatically— for example, by dynamically creating the ListBoxItem objects you need—no virtualization will occur. Of course, you can consider using your own optimization strategy, such as creating just those objects that you need and only creating them at the time they’re needed.