Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DataGridView sort and e.g. BindingList<T> in .NET

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.

like image 864
Matthias Meid Avatar asked Oct 30 '08 10:10

Matthias Meid


2 Answers

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.

like image 197
Sorin Comanescu Avatar answered Oct 14 '22 11:10

Sorin Comanescu


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.

like image 40
Matthias Meid Avatar answered Oct 14 '22 09:10

Matthias Meid