Wednesday, February 21, 2007

Sorting in a DataGrid

There doesn't seem to be a lot of information around on getting sorting in a WinForms DataGrid to work with custom collections. So I've done the hard work for you and here it is. First I wrote a sorting class, hopefully generic enough to sort pretty much any kind of simple data types (if your collection contains other objects you'll probably need to update this somewhat).

  internal class Comparer : IComparer
  {
    private PropertyDescriptor sortProperty;
    private ListSortDirection sortDirection;
    public Comparer(PropertyDescriptor sortProperty, ListSortDirection sortDirection)
    {
      this.sortProperty = sortProperty;
      this.sortDirection = sortDirection;
    }

    #region IComparer Members

    public int Compare(object x, object y)
    {      
      Type propType = sortProperty.PropertyType;
      int sort = 0;
      if (propType == typeof(string))
        sort = string.Compare((string)sortProperty.GetValue(x), (string)sortProperty.GetValue(y));
      else if (propType == typeof(DateTime))
        sort = DateTime.Compare((DateTime)sortProperty.GetValue(x), (DateTime)sortProperty.GetValue(y));
      else if (propType.IsEnum)
        sort = (int)sortProperty.GetValue(x) - (int)sortProperty.GetValue(y);
      else if (propType == typeof(int))
        sort = (int)sortProperty.GetValue(x) - (int)sortProperty.GetValue(y);
      else if (propType == typeof(Type))
        sort = string.Compare(sortProperty.GetValue(x).ToString(), sortProperty.GetValue(y).ToString());
      else if (propType == typeof(bool))
        sort = string.Compare(sortProperty.GetValue(x).ToString(), sortProperty.GetValue(y).ToString());
      else
        throw new NotSupportedException();

      if (sortDirection == ListSortDirection.Descending)
        sort = -sort;

      return sort;
    }

    #endregion
  }

Then I had to implement the IBindingList interface. Actually most of the IBindingList interface is redundant (makes me wonder why it wasn't broken down into a few smaller interfaces, ISortList, ISearchList etc), so here are the bits that are different to the stubs provided by Visual Studio.

    void IBindingList.ApplySort(PropertyDescriptor property, ListSortDirection direction)
    {
      sortProperty = property;
      sortDirection = direction;
      objectList.Sort(new Comparer(sortProperty, sortDirection));
    }

    private PropertyDescriptor sortProperty;
    PropertyDescriptor IBindingList.SortProperty
    {
      get
      {
        return sortProperty;
      }
    }

    bool IBindingList.SupportsSorting
    {
      get
      {
        return true;
      }
    }

    bool IBindingList.IsSorted
    {
      get
      {
        return (sortProperty != null);
      }
    }

    void IBindingList.RemoveSort()
    {
      sortProperty = null;
    }

    private ListSortDirection sortDirection;
    ListSortDirection IBindingList.SortDirection
    {
      get
      {
        return sortDirection;
      }
    }

objectList is my internal ArrayList that contains the collection of objects I'm interested in.

I'm not entirely happy with this solution. I have a lot of collection classes and each one has this same bit of code in it, which always smells a bit bad to me. At some point I might try to create a base class that encapsulates this functionality but it doesn't look too straightforward, since IBindingList requires IList, ICollection and IEnumerable implementations as well... 

4 comments:

Matthias said...

Hey there,

I just stumbled upon you post about the sorting of DataGridViews and that's exactly the problem I have.

I put a piece of code from different examples together and your piece was the last I needed.

Now I have one question left. My class is based on CollectionBase and therefore my List doesn't have a Sort() method I could use. Currently I'm working around this by copying the complete List to an ArrayList, calling Sort() with the comparer and copying back. Really bad...

Do you have an idea what a better solution would be?

Best regards
Matthias

Doogal Bell said...

Hi Matthias

The CollectionBase class has an InnerList property of type ArrayList. Is it not possible to just call Sort on that?

Matthias said...

Hi Doogal,

sooorry for the long delay, but I had to learn for my last diploma test and therefore didn't work on this project.

So the answer is short: you're right. I somehow missed the InnerList. CollectionBase is holding too much stuff. ;)

Now I have another problem as I'm one step further: I'm implementing a filtering, too. My current solution is that I have an additional ArrayList m_OriginalList holding all entries of the list. Everytime a change is made a filtering function is called and puts all matching entries into the InnerList. I saw a similar approach somewehre in the www.

Do you know a better solution?

Best regards
Matthias

Doogal Bell said...

I can't think of a better solution, seems fine to me