views:

4738

answers:

4

Hi Folks

I'm using a BindingList<T> in my Windows Forms that contains a list of "IComparable<Contact>" Contact-objects. Now I'd like the user to be able to sort by any column displayed in the grid.

There is a way described on MSDN online which shows how to implement a custom collection based on BindingList<T> which allows sorting. But isn't there a Sort-event or something that could be caught in the DataGridView (or, even nicer, on the BindingSource) to sort the underlying collection using custom code?

I don't really like the way described by MSDN. The other way I could easily apply a LINQ query to the collection.

Thanks for any help :)

Matthias

A: 

Not for custom objects. In .Net 2.0, I had to roll my on sorting using BindingList. There may be something new in .Net 3.5 but I have not looked into that yet. Now that there is LINQ and the sorting options that come with if this now may be easier to implement.

Darren C
Thanks for your reply. Sorting has become extremely easy with LINQ, but I have found no way to trigger when the user wished to sort the list...
Mudu
+13  A: 

I googled and tried on my own some more time...

There is no built-in way in .NET so far. You have to implement a custom class based on BindingList<T>. One way is described in Custom Data Binding, Part 2 (MSDN). I finally produces a different implementation of the ApplySortCore-method to provide an implementation which is not project-dependent.

protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction)
{
    List<T> itemsList = (List<T>)this.Items;
    if(property.PropertyType.GetInterface("IComparable") != null)
    {
        itemsList.Sort(new Comparison<T>(delegate(T x, T y)
        {
            // Compare x to y if x is not null. If x is, but y isn't, we compare y
            // to x and reverse the result. If both are null, they're equal.
            if(property.GetValue(x) != null)
                return ((IComparable)property.GetValue(x)).CompareTo(property.GetValue(y)) * (direction == ListSortDirection.Descending ? -1 : 1);
            else if(property.GetValue(y) != null)
                return ((IComparable)property.GetValue(y)).CompareTo(property.GetValue(x)) * (direction == ListSortDirection.Descending ? 1 : -1);
            else
                return 0;
        }));
    }

    isSorted = true;
    sortProperty = property;
    sortDirection = direction;
}

Using this one, you can sort by any member that implements IComparable.

Hope it helps to some developers out there.

Matthias

Mudu
1+ Thank it's really helpfull
Abdullah BaMusa
+1. Why didn't MS just implement this dozen lines of code in the class library in the first place?
Not Sure
Also see this post for information on how to emulate the Sort() behavior of List<T> for the SortableBindingList. <http://stackoverflow.com/questions/1063917/bindinglistt-sort-to-behave-like-a-listt-sortAlso note that the suggestion was made to disable notifications while sorting. An example of how to do that is included in the referenced discussion
Paul Prewett
+2  A: 

I higly appreciate Matthias' solution for its simplicity and beauty.

However, while this gives excellent results for low data volumes, when working with large data volumes the performance is not so good, due to reflection.

I ran a test with a collection of simple data objects, counting 100000 elements. Sorting by an integer type property took around 1 min. The implementation I'm going to further detail changed this to ~200ms.

The basic idea is to benefit strongly typed comparison, while keeping the ApplySortCore method generic. The following replaces the generic comparison delegate with a call to a specific comparer, implemented in a derived class:

New in SortableBindingList<T>:

protected abstract Comparison<T> GetComparer(PropertyDescriptor prop);

ApplySortCore changes to:

protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
{
    List<T> itemsList = (List<T>)this.Items;
    if (prop.PropertyType.GetInterface("IComparable") != null)
    {
        Comparison<T> comparer = GetComparer(prop);
        itemsList.Sort(comparer);
        if (direction == ListSortDirection.Descending)
        {
            itemsList.Reverse();
        }
    }

    isSortedValue = true;
    sortPropertyValue = prop;
    sortDirectionValue = direction;
}

Now, in the derived class one have to implement comparers for each sortable property:

class MyBindingList:SortableBindingList<DataObject>
{
        protected override Comparison<DataObject> GetComparer(PropertyDescriptor prop)
        {
            Comparison<DataObject> comparer;
            switch (prop.Name)
            {
                case "MyIntProperty":
                    comparer = new Comparison<DataObject>(delegate(DataObject x, DataObject y)
                        {
                            if (x != null)
                                if (y != null)
                                    return (x.MyIntProperty.CompareTo(y.MyIntProperty));
                                else
                                    return 1;
                            else if (y != null)
                                return -1;
                            else
                                return 0;
                        });
                    break;

                    // Implement comparers for other sortable properties here.
            }
            return comparer;
        }
    }
}

This variant requires a little bit more code but, if performance is an issue, I think it worths the effort.

Sorin Comanescu
A: 

Here is an alternative that is very clean and works just fine in my case. I already had specific comparison functions set up for use with List.Sort(Comparison) so I just adapted this from parts of the other StackOverflow examples.

class SortableBindingList<T> : BindingList<T>
{
 public SortableBindingList(IList<T> list) : base(list) { }

 public void Sort() { sort(null, null); }
 public void Sort(IComparer<T> p_Comparer) { sort(p_Comparer, null); }
 public void Sort(Comparison<T> p_Comparison) { sort(null, p_Comparison); }

 private void sort(IComparer<T> p_Comparer, Comparison<T> p_Comparison)
 {
  if(typeof(T).GetInterface(typeof(IComparable).Name) != null)
  {
   bool originalValue = this.RaiseListChangedEvents;
   this.RaiseListChangedEvents = false;
   try
   {
    List<T> items = (List<T>)this.Items;
    if(p_Comparison != null) items.Sort(p_Comparison);
    else items.Sort(p_Comparer);
   }
   finally
   {
    this.RaiseListChangedEvents = originalValue;
   }
  }
 }
}
Dan Koster