Custom sorting in the DataGridView

Posted on December 10, 2006

How do I control the sort order for a column in the DataGridView?

This question comes up when the “natural” order of the sorted data is at odds with the user’s expectation.  Let’s say that you have a customer table with a customer program column.  This column indicates the customer rewards level for a loyalty program.  For our example, the possible values might be:

  • None
  • Silver
  • Gold
  • Platinum

If these values are represented as strings, the order in an ascending sort will be:

  • Gold
  • None
  • Platinum
  • Silver

But our users will expect an order like this:

  • None
  • Silver
  • Gold
  • Platinum

In other words, ordered by rank.

The DataGridView knows nothing of this ranking, of course.  The default sorting scenario (automatic sorting for a data-bound column) of invoking DataGridView.Sort() via a column header mouse click simply delegates to the IBindingList implementation of ApplySort() in the list you’ve bound to the grid.  Normally, this list would be a DataView.  Using my ObjectListView implementation, this would be a view of a list of arbitrary business objects.  Either way, you end up comparing the properties of the items in the list using the IComparable implementation of the property type.

DataGridView.Sort

What about Sort()?  There’s an overload that allows you to provide an IComparer.  Aha!  Well, sort of.  This call doesn’t support the data-bound scenario; if you’ve set the DataSource on the grid, Sort(IComparer) will throw an InvalidOperationException().  Meeting the custom sorting requirement then leads you to unbound or virtual mode.  I haven’t worked with virtual mode, so I won’t comment on it other than to say that it looks mighty complicated.  Virtual mode provides the programmer with very fine-grained control over the interaction between the grid and the data, at the expense of adding significant complexity to your model.  There are some good examples in the MSDN documentation if you decide to go down this path.

In unbound mode, the SortCompare event is raised by the grid for each item comparison that needs to be done during a sort operation.  This allows you to implement custom sorting.  But of course, this only works for unbound mode – meaning that you can’t use a DataSource.

The ObjectListView Solution

Since there’s no custom sorting mechanism for the mainstream DataGridView bound mode scenario, I decided to add support for custom sorting in ObjectListView.  ObjectListView is my implementation of a DataView-like class for arbitrary business objects.  It allows you to bind a DataGridView (and other controls) to an IList of your choice.  The ObjectListView can then be sorted or filtered as needed, without any impact on the underlying data.  Your list items are not rearranged or removed during sorting and filtering; only the view changes.

My original sorting mechanism relied on the IComparable implementation of each property in the sort, much like the sorting in a DataView.  To supplement this, I’ve added the PropertyComparers collection to ObjectListView.  To perform custom sorting, you simply add an IComparer to the collection for the property or properties you’re sorting on.

private void radioButtonSortProgram_CheckedChanged(object sender, ventArgs e)
{
    if (this.radioButtonSortProgramAlpha.Checked)
        this.view.PropertyComparers["Program"] = new CustomerProgramComparerAlpha();
    else if (this.radioButtonSortProgramRank.Checked)
        this.view.PropertyComparers["Program"] = new CustomerProgramComparerRank();
    else
        this.view.PropertyComparers.Remove("Program");
}

Given an instance of ObjectListView (this.view), the above code shows how one could change the sorting strategy for a column on the fly.  The items in the list bound to the DataGridView each have a property named Program.  Depending on the radio button selected, the ObjectListView will use a CustomerProgramComparerAlpha, a CustomerProgramComparerRank, or the default IComparable implementation of the Program property type to compare items during a sort.  The comparer classes implement IComparable:

public class CustomerProgramComparerAlpha : IComparer
{
    public int Compare(object x, object y)
    {
        return string.Compare(x.ToString(), y.ToString());
    }
}

public class CustomerProgramComparerRank : IComparer
{
    public int Compare(object x, object y)
    {
        CustomerProgram p1 = (CustomerProgram)x;
        CustomerProgram p2 = (CustomerProgram)y;

        if (p1 == p2)
            return 0;

        switch (p1)
        {
            case CustomerProgram.Platinum:
                return 1;
            case CustomerProgram.Gold:
                return (p2 == CustomerProgram.Platinum) ? -1 : 1;
            case CustomerProgram.Silver:
                return (p2 == CustomerProgram.None) ? 1 : -1;
            default:
                return -1;
        }
    }
}

The CustomerProgramComparerAlpha implementation just compares the string representation of the two property values.  CustomerProgramCompareRank compares the values using the ordering that the user would expect to see.

The code above is part of the larger Master/Details example in the ObjectListView download.

Details

The PropertyComparers property exposes a PropertyComparersCollection, which is a dictionary of property name keys and IComparer values.  You can replace the IComparer for a property by using the PropertyComparersCollection.Add() method or the indexer (e.g. view.PropertyComparers[“PropName”] = myComparer).  To revert to the default IComparable sorting, use the PropertyComparersCollection.Remove() method or set the IComparer value to null via the indexer.

If an IComparer is added to the PropertyComparers collection, it will be used for all subsequent sorts until it is removed from the collection or it is replaced with another IComparer.  If the ObjectListView is already sorted when an IComparer is added to or removed from PropertyComparers, the view will automatically be re-sorted.

If you want to change multiple property comparers after the view is sorted, you can use the ObjectListView BeginUpdate() and EndUpdate() methods to suppress the ListChanged and Sort events until all of the IComparers have been changed.  This prevents multiple refreshes of the DataGridView.  If the ObjectListView is not sorted at the time IComparers are added or removed, no automatic re-sorting is done.

Note that when sorting on multiple columns, a custom IComparer can be used with one sort property, and the default IComparable sort on another.

What’s Next?

Since we’re headed down the track of custom this-and-that, I think it would be cool to allow filtering through a user-supplied predicate, rather than specifying filter conditions in a string (e.g. view.Filter = “Program > Silver”).  A string filter expression syntax must be applicable to a potentially wide variety of property types, and must be somehow boiled down to an IComparable implementation.  This implies that type conversion from a string to the target type is supported.  There is also an implicit constraint that each property type must implement IComparable.  With a custom predicate, you could add your own code to specify whether or not a particular list item should be included in the filter, without the need to support IComparable or string type conversion in the property type.  Look for a custom filter implementation next weekend.  Until then, happy sorting!


No Replies to "Custom sorting in the DataGridView"


    Got something to say?

    Some html is OK