I have a performance problem in my WPF application. (I guess that's not a new thing...) Basically the application contains of a ListBox
with items that can be selected, and a ContentControl
that shows details for the selected item. For one type of item, there's an ItemsControl
involved that displays a list of subitems. And this is the problem. As soon as I remove that ItemsControl
, or only have it display a very small number of subitems (< 3), it's fast. But with 50 subitems, browsing through the list feels way too slow.
You'd probably suggest me some sort of virtualisation, but that doesn't apply here. In many cases, all items will fit on the screen, so they need to be displayed immediately anyway. No need to virtualise items that are out of sight.
I could strip down my whole application to just a few short classes (views and view models) that demonstrate the performance issue. This test case can be downloaded here. Just run the application, select an item from the list on the left side and move to other items by pressing and holding the up or down arrow keys. Normally, this shows the other item instantly, but here it takes a very noticeable time of ~160 ms each.
The core of the view looks like this:
<UserControl ...>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding StackFrameVMs, Mode=OneTime}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="•" Foreground="{Binding ...}"/>
<TextBox Margin="4,0,0,0" Text="{Binding ...}"/>
<TextBox Margin="12,0,0,0" Text="{Binding ...}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</UserControl>
That's not a lot of UI controls, but I need them all. Actually there's a few more controls and bindings in my real application, but these are sufficient for the demo. When I move the contents of the DataTemplate
to a separate UserControl
and insert that instead, it takes even longer.
From previous profilings I believe that ItemsControl
throws away and recreates all controls for the list items every time the list changes, because a different item is selected (and DataContext
of the details view changes). Even if the view itself is reused because the new selected item has the same type and uses the same DataTemplate
.
Is there a way to make ItemsControl
reuse the items it has once created? Maybe even all of them, if one of the selected items needs fewer but the next needs more again? Or is there any way to improve the performance for this very simple use case in another way?
Update: BTW, note that this example code is a very much stripped down version of what my full application looks like. You might think you could simplify the structure I chose, but consider that the full application looks something like the following screenshot. There's more than just the ItemsControl
, and there are different detail views for different item types selectable from the left list. So I basically need the structure I have, it just needs to be faster.
The whole project is open source, you may take a look at the complete solution if you like: https://github.com/dg9ngf/FieldLog
I've found a solution but at the cost of start-up time.
Put the right hand list in an
ItemsControl
with aGrid
as its panel, this will load every item at the start up but with a little change you can make it look just like before:Now add this to
MainViewModel
:And add this to
FieldLogExceptionItemViewModel
:Then in order to speed it up even more:
Notice the
BooleanToVis
Converter. you need to implement a newBooleanToVisibilityConvereter
because the system default converter usesVisibility.Collapsed
when passed false and it causes the UI to reload the collapsed item after it's changed back toVisibility.Visibile
, but what we need is aVisibility.Hidden
since we need to keep everything ready at any time.