如何提高StackPanel中巨型ListBox的性能?

8
我正在使用StackPanel垂直布置多个控件(例如标题,子标题,列表框,分隔符,列表框等)。StackPanel是ScrollViewer的子级,以确保其内容始终可滚动。StackPanel中的控件之一是ListBox。它的ItemsSource数据绑定到一个巨大的集合,并使用复杂的DataTemplate来实现每个项目。不幸的是,这导致了非常差的性能(高CPU/内存)。我尝试将ListBox的ItemsPanel设置为VirtualizingStackPanel,并覆盖其ControlTemplate仅使用ItemsPresenter(删除ListBox的ScrollViewer),但是性能没有任何改进。我猜测StackPanel在测量期间会给其内部子元素提供无限高度?当我用其他面板/布局(例如Grid,DockPanel)替换ScrollViewer和StackPanel时,性能显着提高,这表明瓶颈以及解决方案都在虚拟化中。是否有任何方法可以改善此视图的CPU/内存性能?

enter image description here

[更新1]

原始样例项目:http://s000.tinyupload.com/index.php?file_id=29810707815310047536

[更新2]

我尝试重新设计/模板化TreeView/TreeViewItems来得到以下示例。它仍然需要很长时间才能启动/相同,内存使用率很高。但一旦加载完成,滚动感觉比原始示例更具响应性。

想知道是否有其他方法进一步改善启动时间/内存使用情况?

重新设计的TreeView项目:http://s000.tinyupload.com/index.php?file_id=00117351345725628185

[更新2]

pushpraj的解决方案非常好。

  • 原始数据:
    • 启动时间:35秒
    • 内存占用:393MB
    • 滚动:缓慢
  • 树形视图:
    • 启动时间:18秒
    • 内存占用:377MB
    • 滚动:快速
  • pushpraj的解决方案:
    • 启动时间:<1秒
    • 内存占用:20MB
    • 滚动:快速
1个回答

20

你可以限制巨型列表框的最大尺寸并启用虚拟化。

例如:

<ListBox MaxHeight="500" 
         VirtualizingPanel.IsVirtualizing="true" 
         VirtualizingPanel.VirtualizationMode="Recycling" />

这将使ListBox仅加载少量项目,并在需要时在listbox上启用滚动条滚动其余项目。

同时设置VirtualizationModeRecycling将帮助您重复使用复杂的数据模板,从而消除了为每个项目重新创建它们的需要。


编辑

这是基于您的示例的解决方案,我使用了CompositeCollectionVirtualization来实现所需的效果。

xaml

<Grid xmlns:sys="clr-namespace:System;assembly=mscorlib"
      xmlns:l="clr-namespace:PerfTest">
    <Grid.Resources>
        <DataTemplate DataType="{x:Type l:Permission}">
            <StackPanel Orientation="Horizontal">
                <CheckBox />
                <TextBlock Text="{Binding Name}" />
                <Button Content="+" />
                <Button Content="-" />
                <Button Content="..." />
            </StackPanel>
        </DataTemplate>
        <CompositeCollection x:Key="data">
            <!-- Content 1 -->
            <TextBlock Text="Title"
                       FontSize="24"
                       FontWeight="Thin" />
            <!-- Content 2 -->
            <TextBlock Text="Subtitle"
                       FontSize="16"
                       FontWeight="Thin" />
            <!-- Content 3 -->
            <CollectionContainer Collection="{Binding DataContext, Source={x:Reference listbox}}" />
            <!-- Content 4 -->
            <TextBlock Text="User must scroll past the entire list box before seeing this"
                       FontSize="16"
                       FontWeight="Thin"
                       Padding="5"
                       TextWrapping="Wrap"
                       Background="#99000000"
                       Foreground="White" />
        </CompositeCollection>
    </Grid.Resources>
    <ListBox x:Name="listbox"
             VirtualizingPanel.IsVirtualizing="True"
             VirtualizingPanel.VirtualizationMode="Recycling"
             ScrollViewer.HorizontalScrollBarVisibility="Disabled"
             ItemsSource="{StaticResource data}" />
</Grid>

代码

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        var items = new ObservableCollection<Permission>();
        foreach (var i in Enumerable.Range(0, 10000).Select(i => new Permission() { Name = "Permission " + i }))
        { items.Add(i); }
        DataContext = items;
    }
}

public class Permission
{
    public string Name { get; set; }
}

由于无法为字符串创建数据模板,因此我将字符串集合更改为 Permission 集合。希望在您的实际项目中会有类似的情况。

尝试一下,看看是否接近您所需的内容。

注意:如果在 Collection="{Binding DataContext, Source={x:Reference listbox}}" 上出现任何设计者警告,您可以安全地忽略它。


修复列表框的最大高度会导致出现2个滚动条(请参见http://i.imgur.com/s1MJ6BO.png?1) - jayars
1
这是正确的,但同时虚拟化只能在高度固定或最大高度已定义时才能工作,它无法适用于无限高度。您可以使用组合集合将stackpanel中的所有项组合并启用虚拟化,在listbox中显示它们。如果您能分享代码,我会尽力帮助您。 - pushpraj
更新包括示例项目。 - jayars
@jsjslim 我更新了我的答案以满足你的需求。现在它加载得非常快,没有额外的滚动条。 - pushpraj
1
谢谢!非常快速的启动,低内存使用率。我一定需要开始探索随 .NET/WPF 一起提供的其他类。 - jayars

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