We are in a phase to convert our application from SilverLight to WPF, where we have developed our own custom DataGrid on top Silverlight's native one. Application consists of a large number of views out of which nearly 99% views are using these custom grids.
One of the biggest challenge in this excercise that we are facing is the performance of grid in WPF version of application in comparison to SilverLight. In particular filtering is extremely slow!
We have created two test applications to compare and profile Silverlight and WPF on the native controls. The applications load the same set of data: 1000 rows of mixed data - text, bool, int and enum 25 columns bound to the mixed data (5 identical columns duplicated 5 times)
From profiling we have discovered that the Silverlight datagrid seems to have a specific logic handling NotifyCollectionChanged.Reset event from the filtered collection view. The net result is that the whole filtering and layout operation is handled in ~700ms.
In contrast, the WPF datagrid does not have this logic, and so the NotifyCollectionChanged.Reset is intercepted by the ItemCollection and in turn a Panel, which leads to a very deeply nested call tree with a lot of recursive calls through a class called DescendantsWalker. This is followed by a full UpdateLayout cycle. In total WPF takes ~3.7 seconds for the same filtering operation.
Has anyone been through a similar migration, and did you experience the same issue? Did you manage to find a solution with the native WPF datagrid?
The data can be seen below. The profiler (dotTrace) snapshots for SilverLight and WPF, and an excerpt of the source code used.
SilverLight results
WPF results:
1: Filtering operation with deeply nested call tree
2: UpdateLayout Cycle
Source code:
public class Data
{
private CollectionViewSource viewSource = new CollectionViewSource();
public Data()
{
var items = new List<Item>();
// Create 1000 items
viewSource.Source = items;
}
public ICollectionView View => viewSource.View;
}
public class Item
{
public string Description { get; set; } = "Text here";
public bool Bool { get; set; }
public int Value { get; set; }
public DateTime TimeStamp { get; set; }
public Test EnumValue { get; set; }
public static IEnumerable AvailableEnumValues =>
Enum.GetValues(typeof(Test));
}
<DataGrid ItemsSource="{Binding View}"
EnableColumnVirtualization="True" EnableRowVirtualization="True"
VirtualizingPanel.IsContainerVirtualizable="False"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingStackPanel.CacheLength="2,3"
VirtualizingStackPanel.CacheLengthUnit="Page"
VirtualizingStackPanel.ScrollUnit="Item">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Description}" Width="100"/>
<DataGridTextColumn Binding="{Binding Value}" Width="100"/>
<DataGridCheckBoxColumn Binding="{Binding Bool}" Width="100"/>
<DataGridComboBoxColumn SelectedItemBinding="{Binding EnumValue}"
ItemsSource="{x:Static local:Item.AvailableEnumValues}"
Width="100"/>
<DataGridTextColumn Binding="{Binding TimeStamp}" Width="100"/>
<!-- Repeat 5 columns above 5 times -->
</DataGrid.Columns>
</DataGrid>