Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Improve performance for huge ListBox in StackPanel?

I am using a StackPanel to layout several controls vertically (ie, Title, sub titles, listbox, separator, listbox, etc).

The StackPanel is a child of a ScrollViewer to ensure its content is always scrollable.

One of the controls in the StackPanel is a ListBox.

Its ItemsSource is data bound to a huge collection, and a complex DataTemplate is used to realise each item.

Unfortunately, I'm getting really poor performance (high cpu/memory) with it.

I tried

  • setting the ListBox's ItemsPanel to a VirtualizingStackPanel, and
  • overriding its ControlTemplate to only an ItemsPresenter (remove the ListBox's ScrollViewer).

But there were no difference in performances. I'm guessing the StackPanel gives its internal children infinite height during measure?

When I replaced the ScrollViewer and StackPanel with other panels/layouts (e.g, Grid, DockPanel) and the performance improves significantly, which leads me to believe the bottleneck, as well as solution, is in virtualization.

Is there any way for me to improve the cpu/memory performance of this view?

enter image description here

[Update 1]

Original Sample project: http://s000.tinyupload.com/index.php?file_id=29810707815310047536

[Update 2]

I tried restyling/templating TreeView/TreeViewItems to come up with the following example. It still takes a long time to start/same,high memory usage. But once loaded, scrolling feels a lot more responsive than the original sample.

Wonder if there's any other way to further improve the start up time/memory usage?

Restyled TreeView project: http://s000.tinyupload.com/index.php?file_id=00117351345725628185

[Update 2]

pushpraj's solution works like a charm

  • Original:
    • Startup: 35s,
    • Memory: 393MB
    • Scrolling: Slow
  • TreeView:
    • Startup: 18s,
    • Memory 377MB,
    • Scrolling: Fast
  • pushpraj's solution:
    • Startup: <1s,
    • Memory: 20MB,
    • Scrolling: Fast
like image 637
jayars Avatar asked Aug 27 '14 14:08

jayars


People also ask

Why is WPF application slow in performance?

The unnecessary elements in the visual tree also contribute to the slowness of WPF applications. You should ideally combine the layout and optimize the default control templates.

What is VirtualizingStackPanel?

The VirtualizingStackPanel calculates the number of visible items and works with the ItemContainerGenerator from an ItemsControl (such as ListBox or ListView) to create UI elements only for visible items.

How do you make a StackPanel scroll?

Put ScrollViewer inside StackPanel . You could use a ListBox instead. It will scroll automatically.

What is virtualization WPF?

Virtualization technique in WPF improves the rendering performance of UI elements. By applying virtualization, the layout system ensures that only the visible items of a container are rendered on the screen.


1 Answers

you may perhaps limit the maximum size of the huge list box and enable Virtualization

eg

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

this will enable the ListBox to load a few items only and will enable a scrollbar on listbox to scroll to rest of the items if needed.

at the same time setting VirtualizationMode to Recycling will help you to reuse the complex data templates thus eliminating the need of re creating them again for every item.


EDIT

here is a solution based on your sample, I have used CompositeCollection with Virtualization to achieve the desired.

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>

code

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

since we can not create data template for string so I changed the string collection to Permission collection. I hope in your real project it would be something similar.

give this a try and see if this is close to what you need.

note: you may safely ignore if there is any designer warning on Collection="{Binding DataContext, Source={x:Reference listbox}}"

like image 109
pushpraj Avatar answered Oct 10 '22 07:10

pushpraj