ListView使用可变项目的大小

时间:2016-05-09 15:34:39

标签: wpf performance listview layout

将ListView与数千个这样的项目一起使用:

<ListView ItemsSource="{Binding Items}"
                 VirtualizingPanel.ScrollUnit="Pixel"
                 VirtualizingPanel.VirtualizationMode="Recycling"
                 VirtualizingPanel.CacheLength="1,1">
    <ListView.ItemTemplate>
        <DataTemplate>
            <local:ItemUserControl />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

如果我在ItemUserControl中使每个项目具有不同的高度,我发现滚动速度非常慢。通过拖动拇指快速滚动,每个帧大约需要 200-250 ms 才能进行布局。

如果我将VirtualizationMode更改为Standard,则会降至70-110毫秒 如果我另外将ScrollUnit更改为Item,则会降至30-70毫秒 但如果我像以前一样保留所有内容并简单地强制每个项目都相同Height,那么它会下降到 5-7 ms

此外,我发现在列表顶部滚动时,不同高度的滚动速度更快,而在底部滚动较慢。

我的猜测是,当重用ItemUserControl更改Height时,会导致整个ListView的布局更新。虽然这并没有真正解释为什么它在开始时比在结束时表现更好。我也觉得这种速度差异有点难以置信。

任何人都可以更深入地了解WPF的布局系统,并解释为什么会这样?也许是一个可以实现&lt; 10ms布局时间和高度变化的解决方案?

2 个答案:

答案 0 :(得分:0)

感谢DeanChalk质疑我的理智,这让我质疑WPF的理智,这导致了这个答案。

事实证明,虽然ListView存在不同大小的项目问题,但ItemsControl却没有。 ListView继承自ItemsControl,但必定会有一些问题导致问题。

所以使用ItemsControl就是答案。

此代码将为ItemsControl启用虚拟化,并使用与之前相同的指标,在​​快速滚动时执行大约20 ms的布局。

<ItemsControl ItemsSource="{Binding Items}"
              VirtualizingPanel.ScrollUnit="Pixel"
              VirtualizingPanel.VirtualizationMode="Recycling"
              VirtualizingPanel.CacheLength="1,1"
              VirtualizingPanel.IsVirtualizing="True"
              ScrollViewer.CanContentScroll="True">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <local:ItemUserControl />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.Template>
        <ControlTemplate>
            <ScrollViewer>
                <ItemsPresenter />
            </ScrollViewer>
        </ControlTemplate>
    </ItemsControl.Template>
</ItemsControl>

它似乎仍然比它可能更慢,但它是可用的 滚动顶部与底部之间的速度差异仍然存在,我无法解释它。如果有人有一些见解,请发表评论。

答案 1 :(得分:0)

这一切都归结为滚动位置的大小/位置所需的数学。

A)如果项目都是固定大小,则滚动是Pixel:

  • 滚动高度= Items.Count * Item.Height
  • First Visible Item = Scroll Offset(Pixels)/ Item.Height

B)如果项目的高度可变,但滚动是按项目:

  • 滚动高度= Items.Count
  • First Visible Item = Scroll Offset(Items)

C)如果项目的高度可变,并且滚动是Pixel:

  • 滚动高度=项目的总和。所有项目的高度
  • 第一个可见项目=滚动偏移(像素) - 前N个项目的高度;必须搜索N。

可以对选项C进行一些优化,可能会也可能不会内置到WPF中:

  • 滚动高度可以近似而不是准确。也许使用到目前为止测量过的物品的平均物品高度。
  • 第一个可见项目计算可以通过二分搜索完成,但仍可能需要实际测量第一个可见项目上方的所有项目。

基本上,行为C将始终是虚拟化项目列表的最坏情况,因为它可能需要加载所有项目,只是为了进行布局测量。