虚拟化ItemsControl?

139

我有一个包含数据列表的ItemsControl,我想对其进行虚拟化,但是使用VirtualizingStackPanel.IsVirtualizing="True"ItemsControl上似乎不起作用。

这是真的吗?或者有其他我不知道的方式可以实现吗?

为了测试,我一直在使用以下代码块:

<ItemsControl ItemsSource="{Binding Path=AccountViews.Tables[0]}"
              VirtualizingStackPanel.IsVirtualizing="True">
<ItemsControl.ItemTemplate>
    <DataTemplate>
        <TextBlock Initialized="TextBlock_Initialized"  
                   Margin="5,50,5,50" Text="{Binding Path=Name}" />
    </DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

如果我将ItemsControl更改为ListBox,我可以看到Initialized事件仅运行了少数几次(巨大的边距只是为了我只需要浏览少量记录),但是对于ItemsControl,每个项都会被初始化。

我尝试将ItemsControlPanelTemplate设置为VirtualizingStackPanel,但似乎没有帮助。

3个回答

248

实际上,要实现虚拟化并不仅仅是让ItemsPanelTemplate使用VirtualizingStackPanel。对于ItemsControl来说,默认的ControlTemplate没有ScrollViewer,而这是实现虚拟化的关键。通过使用ListBox的控件模板为ItemsControl添加默认控件模板,我们得到了以下结果:

<ItemsControl ItemsSource="{Binding AccountViews.Tables[0]}">
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <TextBlock Initialized="TextBlock_Initialized"
                 Text="{Binding Name}" />
    </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>

顺便提一下,查看默认控件模板的好工具是Show Me The Template

需要注意以下几点:

你必须设置 ScrollViewer.CanContentScroll="True",详见这里

还要注意我加上了 VirtualizingStackPanel.VirtualizationMode="Recycling"。这将缩减对于可见文本块数量而言 TextBlock_Initialized 函数被调用的次数。你可以在这里阅读更多关于 UI 虚拟化的内容:这里

编辑:忘了说一个显然的解决方案:你可以直接把 ItemsControl 替换成 ListBox :) 此外,请查看这个MSDN 上的优化性能页面,并且留意到 ItemsControl 不在“实现性能特性的控件”列表中,这就是为什么我们需要编辑控件模板的原因。


1
谢谢,这正是我在寻找的东西!我正在寻找一种与列表框不同的选择行为,当时我认为使用项控件可能是最简单的方法。 - Rachel
1
如果这个ItemsControl进一步嵌套,您还应该给它一个高度。否则,滚动条将不会显示。 - buckley
9
请注意,我将VirtualizingStackPanel.VirtualizationMode = Recycling添加到了代码中。这不是你提供的示例吗? - buckley
1
我的内容在滚动滚动条时没有滚动,有什么可能导致这种情况? - mrid
5
ScrollViewer.CanContentScroll="True" 是实现虚拟化必需的,但它有一个缺点,即使在列表末尾会导致不需要的空间,因为它使用基于项目而不是像素的处理方式。然而,自 .NET 4.5 以来,可以通过将 VirtualizingPanel.ScrollUnit="Pixel" VirtualizingPanel.IsContainerVirtualizable="True" 添加到 ItemsControl 来解决这个问题。 - RonnyR
显示剩余5条评论

55

在DavidN的回答基础上,以下是您可以在ItemsControl上使用以进行虚拟化的样式:

<!--Virtualised ItemsControl-->
<Style x:Key="ItemsControlVirtualizedStyle" TargetType="ItemsControl">
    <Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True"/>
    <Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <VirtualizingStackPanel />
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ItemsControl">
                <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>
        </Setter.Value>
    </Setter>
</Style>

我不喜欢使用ListBox的建议,因为它们允许选择你并不一定想要的行。


-4

问题在于默认的ItemsPanel不是一个VirtualizingStackPanel。你需要进行更改:

<ItemsControl>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

9
我正在将其投下反对票,因为解决方案不完整。您需要在模板中使用滚动查看器以启用虚拟化。 - Saraf Talukder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接