Improving Microsoft DataGrid CTP sorting performance

Summary:

As you may know Microsoft released a Community Tech Preview (CTP) of the DataGrid control. See the posting here.
Once you start using the DataGrid CTP, load many elements to it and you use the default, built-in sort (by simply clicking on the DataGrid column headers) you may notice that sorting can be very slow.
The reason is that the comparer that is built-in to the MS DataGrid knows nothing about your data type and currently use reflection which is very costly.

In this blog I wanted to point you to a simple approach that could significantly improve the sorting performance of your DataGrid.

Approach:

If you load many elements to the MS DataGrid and use the built-in sort which currently use reflection, you may find sorting to be very slow.

Fortunately, for many scenarios you can implement your IComparer and provide it in the CustomSort property. You could achieve significant performance gains by doing so, in my example I was able to achieve 100x speed improvement.

In my 300,000 rows DataGrid, sort speed decreased from almost 5 minutes to 2.4 seconds !
(The provided download only loads 6,000 rows, but you are welcome to change...)

image

Code:
 public partial class Window1 : System.Windows.Window
 {
     private bool UseCustomSort
     {
         get { return CustomSortCB.IsChecked == true; }
     }
  
     private void WPF_DataGrid_Sorting(object sender, DataGridSortingEventArgs e)
     {
         if (_stopwatch == null)
         {
             _stopwatch = new Stopwatch();
         }
         else
         {
             _stopwatch.Reset();
         }
  
         _stopwatch.Start();
         if (UseCustomSort)
         {
             e.Handled = true;   // prevent the built-in sort from sorting
             PerformCustomSort(e.Column);
             CustomSortingDone();
         }
         else
         {
             Dispatcher.BeginInvoke(DispatcherPriority.Normal, new NoArgDelegate(StandardSortingDone));
         }
     }
  
     private void StandardSortingDone()
     {
         _stopwatch.Stop();
         statusTextBlock1.Text = "Buit-in Sort: " + _stopwatch.ElapsedMilliseconds.ToString() + " msec";
     }
     private void CustomSortingDone()
     {
         _stopwatch.Stop();
         statusTextBlock2.Text = "Custom Sort: " + _stopwatch.ElapsedMilliseconds.ToString() + " msec";
     }
     private void PerformCustomSort(DataGridColumn column)
     {
  
         ListSortDirection direction = (column.SortDirection != ListSortDirection.Ascending) ? 
                                  ListSortDirection.Ascending : ListSortDirection.Descending;
         column.SortDirection = direction;
         ListCollectionView lcv = (ListCollectionView)CollectionViewSource.GetDefaultView(WPF_DataGrid.ItemsSource);
         MySort mySort = new MySort(direction, column);
         lcv.CustomSort = mySort;  // provide our own sort
     }
  
     public class MySort : IComparer
     {
         public MySort(ListSortDirection direction, DataGridColumn column)
         {
             Direction = direction;
             Column = column;
         }
  
         public ListSortDirection Direction
         {
             get;
             private set;
         }
  
         public DataGridColumn Column
         {
             get;
             private set;
         }
  
         int StringCompare(string s1, string s2)
         {
             if (Direction == ListSortDirection.Ascending)
                 return s1.CompareTo(s2);
             return s2.CompareTo(s1);
         }
  
         int IComparer.Compare(object X, object Y)
         {
             int int1, int2;
             string str1, str2;
             switch ((string)Column.Header)
             {
                 case "Id":
                     int1 = ((Employee)(X)).Id;
                     int2 = ((Employee)(Y)).Id;
                     if (Direction == ListSortDirection.Ascending)
                         return int1.CompareTo(int2);
                     return int2.CompareTo(int1);
                 case "Name":
                     str1 = ((Employee)X).Name;
                     str2 = ((Employee)Y).Name;
                     return StringCompare(str1, str2);
                 // ... do same for other columns
      
               }
             return 0;
         }
     }
    private Stopwatch _stopwatch;
 }

XAML Code:

 <Window x:Class="DataGridSortDemo.Window1"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:sys="clr-namespace:System;assembly=mscorlib"
     xmlns:local="clr-namespace:DataGridSortDemo"
     xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit"
     xmlns:xcdg="http://schemas.xceed.com/wpf/xaml/datagrid"
     Title="Microsoft DataGrid Sort Example" Height="760" Width="850"
     >
     <Window.Resources>
         <local:Employees x:Key="employees" />
     </Window.Resources>
   <Grid>
     <Grid.RowDefinitions>
       <RowDefinition MinHeight="25" Height="Auto"/>
       <RowDefinition MinHeight="3in" Height="*" />
       <RowDefinition Height="Auto" MinHeight="10" />
       <RowDefinition Height="Auto" MinHeight="10" />
       <RowDefinition Height="Auto" MinHeight="10" />
       <RowDefinition Height="Auto" MinHeight="10" />
     </Grid.RowDefinitions>
     <Grid.ColumnDefinitions>
       <ColumnDefinition Width="Auto" />
     </Grid.ColumnDefinitions>
  
  
     <dg:DataGrid x:Name="WPF_DataGrid" FontSize="12pt"
                  Grid.Row="1" Grid.Column="0"  Margin="10,2,10,2"
                  AutoGenerateColumns="False"
                  Sorting="WPF_DataGrid_Sorting"
                  ItemsSource="{StaticResource employees}"
               >
             
       <dg:DataGrid.Columns>
         <dg:DataGridTextColumn Width="Auto" Header="Id" DataFieldBinding="{Binding Path=Id}"></dg:DataGridTextColumn>
         <dg:DataGridTextColumn Width="Auto" Header="Name" DataFieldBinding="{Binding Path=Name}"></dg:DataGridTextColumn>
         <dg:DataGridTextColumn Width="Auto" Header="Position" DataFieldBinding="{Binding Path=Position}"></dg:DataGridTextColumn>
         <dg:DataGridTextColumn Width="Auto" Header="Tel" DataFieldBinding="{Binding Path=Telephone}"></dg:DataGridTextColumn>
         <dg:DataGridTextColumn Width="Auto" Header="Email" DataFieldBinding="{Binding Path=Email}"></dg:DataGridTextColumn>
         <dg:DataGridTextColumn Width="Auto" Header="Enabled" DataFieldBinding="{Binding Path=Enabled}"></dg:DataGridTextColumn>
         <dg:DataGridTextColumn Width="Auto" Header="City" DataFieldBinding="{Binding Path=City}"></dg:DataGridTextColumn>
         <dg:DataGridTextColumn Width="Auto" Header="Country" DataFieldBinding="{Binding Path=Country}"></dg:DataGridTextColumn>
      </dg:DataGrid.Columns>
     </dg:DataGrid>
  
     <TextBlock Grid.Row="2" Name="statusTextBlock1" Foreground="Red"
                FontSize="12pt" Text="" />
     <TextBlock Grid.Row="3" Grid.Column="0" Name="statusTextBlock2" Foreground="Green"
                FontSize="12pt"  Text=""  />
     <CheckBox  Name="CustomSortCB" FontWeight="Bold"  FontSize="12pt" Grid.Row="0" Grid.Column="0" Margin="10,6,6,6"
                Foreground="Blue">
       Use Custom Sort  [Check / Uncheck box, then Click on the DataGrid header to compare Sort performance]
     </CheckBox>
  
  
   </Grid>
 </Window>
Caveats:

You can only use the CustomSort if your DataGrid is bound to an IList. You cannot use the CustomSort if you bound to an instance of IBindingList such as DataView. In this case the class that derives from ICollectionView is a BindingListCollectionView which does not have a custom sort property. The view in this case delegates the Sort logic to the underlying DataView which does not allow sort customization.

DataGridSort.zip