WPF 如何虚拟化 ItemsControl

WPF How to virtualize ItemsControl

我有 List 项,共 774 项。当我将它设置为绑定到 ItemsSource 的 ViewModel 的 Items 属性(也 List)时,它需要大约 10+ 秒。

我已经尝试了 Virtualizing an ItemsControl? 的答案,但它没有用 - 仍然超过 10 秒。

这是未修改的代码。请注意 ItemsControlScrollViewer.

XAML:

<Grid d:DataContext="{x:Static local:RulesListDesignModel.Instance}" Background="{StaticResource ForegroundLightBrush}">
        <ScrollViewer VerticalScrollBarVisibility="Auto">
            <ItemsControl ItemsSource="{Binding Items}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <local:RulesListItemControl />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>
    </Grid>

C#

ViewModelApplication.CurrentRulesListViewModel.Items = mList;

这是XAML根据Virtualizing an ItemsControl?的回答修改代码后的(好像用了10多秒):

<Grid d:DataContext="{x:Static local:RulesListDesignModel.Instance}" Background="{StaticResource ForegroundLightBrush}">
    <ScrollViewer VerticalScrollBarVisibility="Auto">
        <ItemsControl ItemsSource="{Binding Items}"
                      VirtualizingStackPanel.IsVirtualizing="True"
                      ScrollViewer.CanContentScroll="True">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <local:RulesListItemControl />
                </DataTemplate>
            </ItemsControl.ItemTemplate>

            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.Template>
                <ControlTemplate>
                    <Border
        BorderThickness="{TemplateBinding Border.BorderThickness}"
        Padding="{TemplateBinding Control.Padding}"
        BorderBrush="{TemplateBinding Border.BorderBrush}"
        Background="{TemplateBinding Panel.Background}"
        SnapsToDevicePixels="True">
                        <ScrollViewer
                Padding="{TemplateBinding Control.Padding}"
                Focusable="False">
                            <ItemsPresenter
                    SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
                        </ScrollViewer>
                    </Border>
                </ControlTemplate>
            </ItemsControl.Template>
        </ItemsControl>
    </ScrollViewer>
</Grid>

您应该使用 ListBoxListView,它们集成了 ScrollViewer 并且默认启用了 UI 虚拟化。没有必要使用更基本的 ItemsControl.

您应该尝试以下方法来使用 UI 虚拟化:

<ListBox VirtualizingStackPanel.VirtualizationMode="Recycling" />

VirtualizingStackPanel.VirtualizationMode 设置为 VirtualizationMode.Recycling 可提高滚动性能。

如果您想继续使用 ItemsControl(为什么要这样做?),您需要重新设计可视化树。

您目前正在使用两个 ScrollViewers。一个在模板内,一个包裹在 ItemsControl 周围。请注意,由于 ScrollViewer.CanContentScroll 默认为 false,内部 ScrollViewer 负责禁用 UI 虚拟化。将 CanContentScroll 设置为 true 是必不可少的,因为它将滚动单位设置为项目(而不是像素)。 VirtualizingStackPanel 需要知道可见项的数量。

您应该移除外部 ScrollViewer 并且您的性能应该会显着提高:

<ItemsControl ItemsSource="{Binding Items}">
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <local:RulesListItemControl />
    </DataTemplate>
  </ItemsControl.ItemTemplate>

  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <VirtualizingStackPanel IsVirtualizing="True"
                              VirtualizationMode="Recycling" />
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>

  <ItemsControl.Template>
    <ControlTemplate TargetType="ItemsControl">
      <Border BorderThickness="{TemplateBinding BorderThickness}"
              BorderBrush="{TemplateBinding BorderBrush}"
              Background="{TemplateBinding Background}">
        <ScrollViewer CanContentScroll="True" 
                      Padding="{TemplateBinding Padding}"
                      Focusable="False">
          <ItemsPresenter />
        </ScrollViewer>
      </Border>
    </ControlTemplate>
  </ItemsControl.Template>
</ItemsControl>

但更重要的是专注于您的习惯 RulesListItemControl。为每个项目加载此控件。复杂的控件引入了复杂的初始化。你应该尽量减少这个控件的可视化树。

删除所有不需要的 Border,将 Label 替换为 TextBlock,重新访问触发器等。目标是减少每个项目容器的渲染时间。
为此,您需要覆盖用于组成 RulesListItemControl.

的控件的 ControlTemplate