Question

I have an ObservableCollection of ViewModels that are sitting in a WPF DataGrid. The DataGrid has three columns:

  • Position column; this is rendered at runtime by a UserControl that displays the position of the row in my DataGrid
  • Name column; this is rendered at runtime by a UserControl that displays the name of the column (yes, I need a UserControl for this based on how the name needs to be displayed, but that is an aside)
  • Data column; this is rendered at runtime by yet another UserControl.

My columns are defined like this:

        <toolkit:DataGrid.Columns>
            <toolkit:DataGridTemplateColumn Header="" MinWidth="35" MaxWidth="35" SortMemberPath="Position.PositionIndex" CanUserSort="True">
                <toolkit:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ContentPresenter Content="{Binding Path=Position}"/>
                    </DataTemplate>
                </toolkit:DataGridTemplateColumn.CellTemplate>
            </toolkit:DataGridTemplateColumn>
            <toolkit:DataGridTemplateColumn Header="Name" MinWidth="150" Width="150" SortMemberPath="Name" CanUserSort="True">
                <toolkit:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ContentPresenter Content="{Binding Path=Name}"/>
                    </DataTemplate>
                </toolkit:DataGridTemplateColumn.CellTemplate>
            </toolkit:DataGridTemplateColumn>
            <toolkit:DataGridTemplateColumn Header="Data" Width="Auto" CanUserSort="False">
                <toolkit:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ContentPresenter Content="{Binding Path=Data}"/>
                    </DataTemplate>
                </toolkit:DataGridTemplateColumn.CellTemplate>
            </toolkit:DataGridTemplateColumn>
        </toolkit:DataGrid.Columns>

So, because my Row and Name columns are UserControls, the WPF DataGrid cannot natively sort on them. So to facilitate sorting, when a column header is clicked, I do some ListCollectionView.CustomSort magic.

Here is what my custom sorters look like for the Name column:

// Customized sorter, by name, ascending.
public class AscendingNameSorter : IComparer
{
    public int Compare(object x, object y)
    {
        var lhs = (MyViewModel)x;
        var rhs = (MyViewModel)y;

        return lhs.Name.CompareTo(rhs.Name);
    }
}

// Customized sorter, by name, descending.
public class DescendingNameSorter : IComparer
{
    public int Compare(object x, object y)
    {
        var lhs = (MyViewModel)x;
        var rhs = (MyViewModel)y;

        return rhs.Name.CompareTo(lhs.Name);
    }
}

The problem is that this is incredibly slow. I can't figure out why. With 10 items in the DataGrid, my application grinds to a halt for 3-4 seconds while it resorts. I thought ListCollectionView.CustomSort was supposed to be the most efficient way to sort an ObservableCollection... where am I going wrong?

Was it helpful?

Solution

WPF is recreating all of your UserControls every time the sort changes, so my guess is that something in the construction of those controls is slow. But that is only a guess.

You should start by narrowing down the problem. Here are some steps you can take:

  1. Find out which operation is taking 3-4 seconds. You didn't state whether the delay happens only when assigning the value to CustomSort, or every time the list changes after CustomSort has been set. This makes a difference.

  2. Try adding a regular text column and sorting on it using the built-in sort to see whether it is fast or not. Perhaps you have done this already, but you did not say in your question.

  3. For diagnostic purposes temporarily stop setting CustomSort and set ListCollectionView.Filter instead. Set it to a filter that always returns true. If you still get the slowdown, the problem is related to ListCollectionView's attempt to reorganize items.

  4. Temporarily edit your templates and replace your custom UserControls with something trivial (eg <CheckBox/>) to see if things speed up or not.

  5. Set breakpoints in the constructors of your UserControls to see if they are being called the expected number of times (ie 10 constructor calls if there are 10 items in the list). If they are being called more times than expected, look at the stack traces to see where the extra calls are coming from.

  6. Add code to your UserControl constructors to write the DateTime.Now the constructors were called to the output window (or a log, or whatever). This will give you some idea how long each is taking.

  7. Add several hundred items to your ObservableCollection, run your app side-by-side with VS.NET, click your sort button (or whatever), then hit the Break All button in VS.NET and look at the stack trace. Hit Continue and immediately hit Break All again, then look at the stack trace again. Repeat many times. This will give you a good idea of what is taking all the extra time.

If, as I suspect, the problem is slow UserControls creation and binding, you will find: The problem happens on every list change and also happens when you change the Filter, things speed up when you replace your UserControls with <CheckBox/>, your constructor will only be called once per item, the time between calls will be largish.

Note that I'm not saying it is the constructor of the UserControls that is slow - it could be that the UserControl instantiates many sub-objects when it is data-bound, or that it includes objects that are slow or complex, a subobject loads a file, or many other possible causes. The bottom line is that instantiating the DataTemplate on the object and adding it to the visual tree is doing something slow. The stack traces should give you an idea where to look.

If it turns out to be something else or you can't figure it out, just update your question to give more information on what the above tests revealed, and we'll try to help you out.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top