views:

321

answers:

1

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?

+2  A: 

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.

Ray Burns
Great reply, thanks a ton - your answer has got me thinking in a few different ways about this problem, I'll try some of your suggestions out and let you know how it goes! Thanks!
unforgiven3
I quickly tried the Checkbox test - unfortunately, it was blazing fast. I'll look into the UserControl construction on Monday morning :-)
unforgiven3
Okay, so, now that it looks like it is the construction of the UserControl that is holding up the sort, what kinds of things could I do to optimize that? Databinding is a big part of how I present my data, I'm not sure what I can easily do about that.
unforgiven3
I would try the profiling technique (repeatedly hitting Break All and looking at the stack trace) to see what is really slow, then optimize that. If such optimization is impossible, you could set your ItemsSource to a custom view model of your data that includes properties containing precreated UserControls. In that case the grid would simply display the precreated UserControls rather than creating them.
Ray Burns