Is there a way to use virtualization with hidden panels or expanders?

I am trying to improve performance using my WPF application, and I am having problems with the complex ItemsControl element. Although I added virtualization, there is still a performance issue, and I think I decided why.

Each element contains a number of expandable areas. Thus, the user sees the summary at the beginning, but can expand to expand to see more information. Here's what it looks like:

enter image description here

As you can see, there are some nested ItemsControls. Thus, each top-level item has a bunch of hidden controls. Virtualization prevents the loading of elements off-screen, but not hidden elements of the elements themselves. As a result, the relatively simple initial layout takes considerable time. After selecting some of these views, 87% of the time is spent parsing and layout, and loading takes a few seconds.

I would prefer it to take 200ms to expand when (if!) The user decides, rather than 2s, to load the whole page.

Ask for advice really. However, I cannot think of a good way to add controls using MVVM. Is there any extender or visibility-based virtualization supported in WPF, or would I create my own implementation?

The indicator of 87% comes from the diagnosis:

enter image description here

+6
source share
2 answers

If you just

- Expander Container some bindings - Expander Container some bindings + Expander + Expander ... invisible items 

Then yes, the Container and all the bindings are initialized the moment the view is displayed (and the ItemsControl creates a ContentPresenter for the visible elements).

If you want to virtualize the contents of Expander when it crashes, you can use data templates

 public ObservableCollection<Item> Items = ... // bind ItemsControl.ItemsSource to this class Item : INotifyPropertyChanged { bool _isExpanded; public bool IsExpanded // bind Expander.IsExpanded to this { get { return _isExpanded; } set { Data = value ? new SubItem(this) : null; OnPropertyChanged(nameof(Data)); } } public object Data {get; private set;} // bind item Content to this } public SubItem: INotifyPropertyChanged { ... } 

Hopefully there is no need to explain how to make SubItem data SubItem in SubItem .

If you do this, first Data == null and nothing but Expander will be loaded. As soon as it is expanded (by user or programmatically), viewing will create visual effects.

+5
source

I thought I would talk about the details of a solution, which is pretty much a direct implementation of Sinatr's answer.

I used a content control with a very simple data template selector. The template selector simply checks to see if the content item is null and selects between two data templates:

 public class VirtualizationNullTemplateSelector : DataTemplateSelector { public DataTemplate NullTemplate { get; set; } public DataTemplate Template { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { if (item == null) { return NullTemplate; } else { return Template; } } } 

The reason for this is that the ContentControl that I used still exposes the data pattern, even if the content is null. So I installed these two patterns in xaml:

  <ContentControl Content="{Binding VirtualizedViewModel}" Grid.Row="1" Grid.ColumnSpan="2" ><!--Visibility="{Binding Expanded}"--> <ContentControl.Resources> <DataTemplate x:Key="Template"> <StackPanel> ...complex layout that isn't often seen... </StackPanel> </DataTemplate> <DataTemplate x:Key="NullTemplate"/> </ContentControl.Resources> <ContentControl.ContentTemplateSelector> <Helpers:VirtualizationNullTemplateSelector Template="{StaticResource Template}" NullTemplate="{StaticResource NullTemplate}"/> </ContentControl.ContentTemplateSelector> </ContentControl> 

Finally, instead of using a whole new class for the subitem, it’s quite simple to create a “VirtualizedViewModel” object in your view model that references “this”:

  private bool expanded; public bool Expanded { get { return expanded; } set { if (expanded != value) { expanded = value; NotifyOfPropertyChange(() => VirtualizedViewModel); NotifyOfPropertyChange(() => Expanded); } } } public MyViewModel VirtualizedViewModel { get { if (Expanded) { return this; } else { return null; } } } 

I have reduced my 2-3s boot time by about 75%, and now that seems a lot more reasonable.

+1
source

All Articles