How to properly improve the loading of ItemsControl and avoid freezing
I am wondering if this is the right way to improve my loading of ItemsControl. I want to show elements that are quite heavy to load (due to complex XAML) and I don't want my WPF app to hang. I want my objects to be displayed after rendering and to be able to interact with other elements of my window at the same time (even if it's a little slower). Here are some suggestions to get you started:
- Let's say that I cannot improve the load times of my products;
- I cannot use virtualization because I need to see all my items at once;
- The view does not wait for the background operation to complete; all work is done on the UI thread.
I managed to find a workaround, but I'm not happy with it, using BackgroundWorker
and sleep:
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:WpfApplication1">
<DockPanel>
<Button DockPanel.Dock="Bottom" Content="Click me" Click="OnButtonClick"/>
<ItemsControl ItemsSource="{Binding Items}">
<!-- Items begin to be loaded once the ItemsControl is loaded -->
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Width="25" Height="25" Background="Red" Margin="5">
<local:HeavyItem Content="{Binding}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DockPanel>
</Window>
ViewModel.cs
public class ViewModel
{
public ICommand LoadCommand { get; protected set; }
public ObservableCollection<int> Items { get; protected set; }
public ViewModel()
{
Items = new ObservableCollection<int>();
LoadCommand = new DelegateCommand(AsyncLoad);
//Load();
}
protected void Load()
{
for (int i = 0; i < 250; i++)
{
Items.Add(i);
}
}
protected void AsyncLoad()
{
var bk = new BackgroundWorker();
bk.DoWork += (s, e) =>
{
for (int i = 0; i < 250; i++)
{
// Sleep 50ms to let the UI thread breeze
Thread.Sleep(50);
Application.Current.Dispatcher.Invoke(DispatcherPriority.Render, (Action)(() =>
{
Items.Add(i);
}));
}
};
bk.RunWorkerAsync();
}
}
HeavyItem.cs (fake control for heavy visual simulation)
public class HeavyItem : ContentControl
{
protected override Size ArrangeOverride(Size finalSize)
{
Thread.Sleep(20);
return base.ArrangeOverride(finalSize);
}
}
This question suggests a similar approach. I don't like this method because:
- The ViewModel does what the view should do;
- I use a random timer while the element may take longer or less time to render depending on the computer.
I think the best way to do this is to override the ItemsControl and tell the UI thread to update after the item is added, but I was not successful. Any hint or ideas?
source to share
use CollectionView.DeferRefresh () to get one update?
https://blogs.msdn.microsoft.com/matt/2008/08/28/collectionview-deferrefresh-my-new-best-friend/
source to share