Sortable Binding List for custom data objects

I was writing a thin client application where I had to implement some efficiency-oriented business logic, and encountered the problem, that the generic collection of custom data objects can’t be sorted in the DataGridView by simply clicking on the column headers. I started to google up solutions, but either they were for ASP, or they were incomplete, some of them were not usable at all. I ended up using a solution provided on MSDN, but it was far from a complete, bugless solution.

The framework has an own generic abstract class for this situation, called BindingList. The class itself is pretty straightforward, but the code here requires minimun knowledge of the System.ComponentModel and System.Reflection namespace.

Public Class SortableBindingList(Of T)
    Inherits BindingList(Of T)
    Private _isSorted As Boolean
    Private _sortDirection As ListSortDirection
    Private _sortProperty As PropertyDescriptor

    'This override shows the binded object, that our list supports sorting
    Protected Overrides ReadOnly Property SupportsSortingCore() As Boolean
        Get
            Return True
        End Get
    End Property

    'And that it can sort bi-directional
    Protected Overrides ReadOnly Property SortDirectionCore() As ListSortDirection
        Get
            Return _sortDirection
        End Get
    End Property

    'And that it can sort by T typed object's properties
    Protected Overloads Overrides ReadOnly Property SortPropertyCore() As PropertyDescriptor

        Get
            Return _sortProperty
        End Get
    End Property

    'This is the method, what gets called when the sort event occurs in the bound object
    Protected Overloads Overrides Sub ApplySortCore(ByVal prop As PropertyDescriptor, ByVal direction As ListSortDirection)
        Dim items As List(Of T) = TryCast(Me.Items, List(Of T))

        If items IsNot Nothing Then
            Dim pc As New PropertyComparer(Of T)(prop.Name, direction)
            items.Sort(pc)
            _isSorted = True
            _sortDirection = direction
            _sortProperty = prop
        Else
            _isSorted = False
        End If
        OnListChanged(New ListChangedEventArgs(ListChangedType.Reset, -1))
    End Sub

    'This shows if our list is already sorted or not
    Protected Overloads Overrides ReadOnly Property IsSortedCore() As Boolean
        Get
            Return _isSorted
        End Get
    End Property

    'Removing the sort
    Protected Overrides Sub RemoveSortCore()
        _isSorted = False
    End Sub

    Sub New(ByVal list As ICollection(Of T))
        MyBase.New(list)
    End Sub
End Class

The list uses a simple property comparer; the default comparer to compare two properties. It is good for most cases; of course you have to have the correct property type declaration.

 

Public Class PropertyComparer(Of T)
    Implements IComparer(Of T)
    Private _property As PropertyInfo
    Private _sortDirection As ListSortDirection

    Public Sub New(ByVal sortProperty As String, ByVal sortDirection As ListSortDirection)
        _property = GetType(T).GetProperty(sortProperty)
        Me._sortDirection = sortDirection
    End Sub

    Public Function Compare(ByVal x As T, ByVal y As T) As Integer Implements IComparer(Of T).Compare
        Dim valueX As Object = _property.GetValue(x, Nothing)
        Dim valueY As Object = _property.GetValue(y, Nothing)

        If _sortDirection = ListSortDirection.Ascending Then Return Comparer.[Default].Compare(valueX, valueY)

        Return Comparer.[Default].Compare(valueY, valueX)
    End Function
End Class

Comments

I am glad to read this post,

I am glad to read this post, it’s an interesting one.
sending Christmas flowers UK | Christmas gifts UK| Christmas flowers delivered

Nice blog, this will really

Nice blog, this will really enhance our information.
website live chat software

Thanks

Congratulations. Really astounded with the caliber of the advice presented. I sincerely hope that you keep up with the brilliant work accomplished.

Complete Example

Here's a soup-to-nuts implementation:
http://codepaste.net/deke7q
Best of luck. This should be easier.

// Jerry

ruts ploys

wear gout hunt rams flee order elimite online unwed doris Zithromax comma shank convoy spite

Great Post

I've been developing a vb.net / MS SQL application and I'm in the process of converting it to a 3 tier application. It's currently a 2.5 tier application. I was looking for a way to sort unbound datagrid views and your solutions was perfect. Thanks so much.

Sincerely

Thanx indeed!

Thanx indeed!

It is good to see that the

It is good to see that the code posted in here by you requires minimum knowledge of the System.ComponentModel and System.Reflection namespace. I am sure it will be of help to me in case I encounter a similar problem. Will keep this page bookmarked.
electronic deer repellent

JUST GREAT!!

Hi Geri! this is really fantastic! Im using LINQ2SQL and returning a list of entities made it impossible to enable sorting on my datagridview. Im thankful I came across your post here. And just in case anyone's interested Im posting the working C# version below. The only thing I changed in the SortableBindingList constructor is changed the parameter from ICollection<T> to IList<T> - the former threw an error

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections;
using System.Reflection;

public class SortableBindingList<T> : BindingList<T> {
private bool _isSorted;
private ListSortDirection _sortDirection;
private PropertyDescriptor _sortProperty;

//This override shows the binded object, that our list supports sorting
protected override bool SupportsSortingCore {
get { return true; }
}

//And that it can sort bi-directional
protected override ListSortDirection SortDirectionCore {
get { return _sortDirection; }
}

//And that it can sort by T typed object's properties
protected override PropertyDescriptor SortPropertyCore {

get { return _sortProperty; }
}

//This is the method, what gets called when the sort event occurs in the bound object
protected override void ApplySortCore( PropertyDescriptor prop, ListSortDirection direction ) {
List<T> items = this.Items as List<T>;

if (items != null) {
PropertyComparer<T> pc = new PropertyComparer<T>( prop.Name, direction );
items.Sort( pc );
_isSorted = true;
_sortDirection = direction;
_sortProperty = prop;
} else {
_isSorted = false;
}
OnListChanged( new ListChangedEventArgs( ListChangedType.Reset, -1 ) );
}

//This shows if our list is already sorted or not
protected override bool IsSortedCore {
get { return _isSorted; }
}

//Removing the sort
protected override void RemoveSortCore() {
_isSorted = false;
}

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections;
using System.Reflection;

public class PropertyComparer<T> : IComparer<T> {
private PropertyInfo _property;
private ListSortDirection _sortDirection;

public PropertyComparer( string sortProperty, ListSortDirection sortDirection ) {
_property = typeof( T ).GetProperty( sortProperty );
this._sortDirection = sortDirection;
}

public int Compare( T x, T y ) {
object valueX = _property.GetValue( x, null );
object valueY = _property.GetValue( y, null );

if (_sortDirection == ListSortDirection.Ascending) return Comparer.Default.Compare( valueX, valueY );

return Comparer.Default.Compare( valueY, valueX );
}
}

cheers!

This worked well for me,

This worked well for me, except this line:
List items = this.Items as List;

In VS 2010/.Net 4.0, the above "items" was always null, and so nothing could get sorted. So I added implementations of InsertionSort and QuickSort algorithms (I found QuickSort to be much quicker).

Hopefully this will benefit other Googlers that come across this code. You should be able to copy and past this code into a single codefile, though you will need to add a namespace declaration.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections;
using System.Reflection;

public class SortableBindingList : BindingList
{
private bool _isSorted;
private ListSortDirection _sortDirection;
private PropertyDescriptor _sortProperty;

//This override shows the binded object, that our list supports sorting
protected override bool SupportsSortingCore
{
get { return true; }
}

//And that it can sort bi-directional
protected override ListSortDirection SortDirectionCore
{
get { return _sortDirection; }
}

//And that it can sort by T typed object's properties
protected override PropertyDescriptor SortPropertyCore
{

get { return _sortProperty; }
}

//This is the method, what gets called when the sort event occurs in the bound object
protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
{
if (this.Items != null)
{
//this.InsertionSortItems(prop, direction); //Takes longer
this.QuickSortItems(prop, direction);
_isSorted = true;
_sortDirection = direction;
_sortProperty = prop;
}
else
{
_isSorted = false;
}
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
}
///
/// Sort this.Items using QuickSort algorithm.
///
///
///
protected void QuickSortItems(PropertyDescriptor prop, ListSortDirection direction)
{
this.QuickSort(this.Items, 0, this.Items.Count - 1, new PropertyComparer(prop.Name, direction));
}
///
/// To be called recursively, and from QuickSortItems() method.
///
///
///
///
///
protected void QuickSort(IList items, int leftBoundary, int rightBoundary, PropertyComparer comparer)
{

var leftWorkingIndex = leftBoundary;
var rightWorkingIndex = rightBoundary;
double pivotIndex = ((leftBoundary + rightBoundary) / 2);
var pivotItem = items[Convert.ToInt32(pivotIndex)];
T leftItem;
T rightItem;
T tempItem;
while (leftWorkingIndex <= rightWorkingIndex)
{
leftItem = items[leftWorkingIndex];
while (comparer.Compare(leftItem,pivotItem) < 0)
{
leftWorkingIndex++;
leftItem = items[leftWorkingIndex];
}
rightItem = items[rightWorkingIndex];
while (comparer.Compare(rightItem,pivotItem) > 0)
{
rightWorkingIndex--;
rightItem = items[rightWorkingIndex];
}
if (leftWorkingIndex <= rightWorkingIndex)
{
tempItem = items[leftWorkingIndex];
items[leftWorkingIndex] = items[rightWorkingIndex];
leftWorkingIndex++;
items[rightWorkingIndex] = tempItem;
rightWorkingIndex--;
}
}
if (leftBoundary < rightWorkingIndex)
{
QuickSort(items, leftBoundary, rightWorkingIndex,comparer);
}
if (leftWorkingIndex < rightBoundary)
{
QuickSort(items, leftWorkingIndex, rightBoundary,comparer);
}
}
///
/// Sort this.Items using InsertionSort algorithm
///
///
///
protected void InsertionSortItems(PropertyDescriptor prop, ListSortDirection direction)
{
var comparer = new PropertyComparer(prop.Name, direction);
int j;
T item;
for (int i = 1; i < this.Items.Count; i++)
{
item = this.Items[i];
j = i;
while ((j > 0) && (comparer.Compare(this.Items[j - 1], item) > 0))
{
this.Items[j] = this.Items[j - 1];
j = j - 1;
}
this.Items[j] = item;
}

}

//This shows if our list is already sorted or not
protected override bool IsSortedCore
{
get { return _isSorted; }
}

//Removing the sort
protected override void RemoveSortCore()
{
_isSorted = false;
}

public SortableBindingList(IList list)
: base(list)
{
}
}

public class PropertyComparer : IComparer
{
private PropertyInfo _property;
private ListSortDirection _sortDirection;

public PropertyComparer(string sortProperty, ListSortDirection sortDirection)
{
_property = typeof(T).GetProperty(sortProperty);
this._sortDirection = sortDirection;
}
///
/// If return value less than 0, X is less than Y.
/// If return value equals 0, X equals Y.
/// If return value greater than 0, X greater than Y.
///
/// First object to be compared.
/// Second object to be compared.
///
public int Compare(T x, T y)
{
object valueX = _property.GetValue(x, null);
object valueY = _property.GetValue(y, null);

if (_sortDirection == ListSortDirection.Ascending)
return Comparer.Default.Compare(valueX, valueY);

return Comparer.Default.Compare(valueY, valueX);
}
}

Can i get a sample code for framework 4.0?

i got errors on copy peste this class :S

how to instatiate with a list

I see here
that I can make a binding list from a list in the constructor new

dim lc as new List( of Customer)
dim blc as new BindingList(Of Customer)(lc)
dataGridView1.DataSource = blc

But I cant get it to work with the above sortable binding list
Any ideas on how to convert, instantiate, or create the above sortable binding list
like I can the bindinglist by passing a list as a parameter to the constructor?

Reply to How to instantiate

You did everything right on the biding except:
dim lc as new List( of Customer)
Change the
dim blc as new BindingList(Of Customer)(lc)
To
dim blc as new SortableBindingList(Of Customer)(lc)
End Change
dataGridView1.DataSource = blc

A year late :) hope it helps some body

Comparer error?

I am getting the following error on the Comparer.[Default].Compare(valueY, valueX) statement:

Too few type arguments to 'System.Collections.Generic.Comparer(of T)

using VS 2010 and .net 4.0

Erwin

Comparer error!

I think that line should read:

Comparer(Of T).Default.Compare(valueX, valueY)

Instead of

Comparer.[default].compare(valueX,valueY)

I suppose....

Comparer error

Yes, MS might have changed a few framework elements, the code was originally written in VS 2005, .Net 2.0.