WPF Performance and .NET Framework Client Profile

WPF performance and .NET Framework Client Profile related blogs provided by Jossef Goldberg.

Improving Microsoft DataGrid CTP sorting performance

Improving Microsoft DataGrid CTP sorting performance

  • Comments 9
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.

Attachment: DataGridSort.zip
Leave a Comment
  • Please add 4 and 6 and type the answer here:
  • Post
  • PingBack from http://hubsfunnywallpaper.cn/?p=2472

  • This example could be even several times faster if the switch construction was put in the constructor, and sorting code was stored as delegate, as the sorting column is never changed in comparer instance after it's creation :)

  • Jossef just wrote this great post on improving sorting in the DataGrid through a custom sort. Check it

  • I have already checked, great article :)

  • In this blog I wrote how you can improve Microsoft DataGrid CTP by providing your own custom sort.&#160;&#160;

  • Hi Sich

    I updated my sample and, now I am using delegate and I am seeing additional 50% gain.

    See:  

    http://blogs.msdn.com/jgoldb/archive/2008/08/28/improving-microsoft-datagrid-ctp-sorting-performance-part-2.aspx

  • First, thanks for the great coding example - I ended up using your method on a WPF ListView control, which made actually usable (my app has ~ 20,000 rows).

    I did have one issue, where the string properties of some items in the ObservableCollection(Of T) were null (or Nothing, in my case, since I ported it to VB.Net).  The data was coming from a Linq query.

    The way I solved this was to concatenate the value with an empty string (in the mySort switch/Select list), like this:

    Case "FirstName"

          myCompare = Function(a, b) (a.FirstName & "").ToString.CompareTo((b.FirstName & "").ToString) * dir

    I ran your original C# code, adding some items with null values, and it has the same problem, so not just a language issue.  Many thanks :-)

  • Could you explain why we need to invoke StandardSortingDone this way?

    Dispatcher.BeginInvoke(DispatcherPriority.Normal, new NoArgDelegate(StandardSortingDone));

    I see that estimated time is being counted wrong if not to use dispatcher's begininvoke method, but cannot understand it -)

  • And I'd appreciate if You explain why the second way gives additional gain? Is it by avoiding extra "case" conditions checks?

Page 1 of 1 (9 items)