Presenting the SortableBindingList<T> (take two)

I’m in the progress of adding classes that i find interesting to the BeTimvwFramework project. The original implementation of my SortableBindingList<T> relied on IComparable to implement ApplySortCore(PropertyDescriptor property, ListSortDirection direction). I received some good feedback and blogged about those improvements.

Because some of my classes only implement IComparable<T> i needed support for this too. My first thought was to use Comparer<T>:

IComparer comparer = Comparer<t>.Default;
itemsList.Sort(delegate(T t1, T t2)
{
 object property1 = prop.GetValue(t1);
 object property2 = prop.GetValue(t2);
 return reverse * comparer.Compare(property1, property2);
});

Obviously that didn’t work. The problem is that i received the default Comparer for T, instead of the Comparer for the type of the property. Anyway, with a bit of reflection i got access to that Comparer:

Type comparablePropertyType = typeof(Comparer<>).MakeGenericType(property.PropertyType);
IComparer comparer = (IComparer)comparablePropertyType.InvokeMember("Default", BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.Public, null, null, null);

After a bit of refactoring i ended up with a PropertyComparer<T> which allowed me to implement the sorting as following:

Type propertyType = property.PropertyType;

// this cache minimizes the cost of reflection in the PropertyComparer constructor
PropertyComparer<t> comparer;
if (!this.comparers.TryGetValue(propertyType, out comparer))
{
 comparer = new PropertyComparer<t>(property, direction);
 this.comparers.Add(propertyType, comparer);
}
else
{
 comparer.SetListSortDirection(direction);
}

itemsList.Sort(comparer);

In order to write unittests, i needed a way to get instances of a PropertyDescriptor. This was achieved by using the TypeDescriptor as following:

PropertyDescriptor GetPropertyDescriptor(object component, string propertyName)
{
 PropertyDescriptorCollection propertyDescriptors = TypeDescriptor.GetProperties(component);
 foreach (PropertyDescriptor propertyDescriptor in propertyDescriptors)
 {
  if (propertyDescriptor.Name == propertyName)
  {
   return propertyDescriptor;
  }
 }

 throw new ArgumentException(string.Format("The property '{0}' was not found.", propertyName));
}

Feel free to download the updated demo application: sortablebindinglist.zip. There are even real applications that use this class, eg: VSTrac and MonoTorrent. If you’re interested in the unittests you’ll have to get the code at BeTimvwFramework.

This entry was posted in Uncategorized and tagged by timvw. Bookmark the permalink.

38 thoughts on “Presenting the SortableBindingList<T> (take two)

  1. Hi Tim,

    First, let me thank you for providing this source code. It really helped me quickly develop a concrete class for the IBindingList interface that supports sorting. I did have one small piece of feedback though. My business objects have several string properties. If I use your source code as is, the first string property I sort on works fine. However, if I try to sort on a second string property, it uses the sorting from the first string property. I think this is because the PropertyComparer you store for each property type is tied to the first property that was used for sorting. I think your PropertyComparer class needs a new method as follows:

    public void SetPropertyDescriptor(PropertyDescriptor property)
    {
    this.propertyDescriptor = property;

    }

    Then, the ApplySortCore method in your SortableBindingList class needs a new line as follows:

    comparer.SetPropertyDescriptor(property);

    This would go right before:

    comparer.SetListSortDirection(direction);

    So not only does the new sort direction get updated, but the new property to sort on gets updated too. That seemed to fix the problem for me. I hope this all makes sense. You can see the behavior I’m describing if you take your demo app and add first and last name string properties to your Person class. If you sort on last name first, it works fine. But then if you sort on first name, it still uses the sorting from the last name. Again, nice work, I just wanted to offer a slight improvement.

    Regards,

    Scott Ballard
    sd_ballard@hotmail.com

  2. Hello Scott,

    Thanks for the feedback, it allowed me to reproduce the problem quickly.

    It took me a bit more to write a failing unittest that reproduced the behaviour because there was a bug in my unittests :/

    Anyway, the problem has been solved now and the code has been committed into the BeTimvwFramework repository. I have uploaded a fixed demo application.

  3. Very nice saved me a bit of grief and given how simple it is I can’t see why the doesn’t DGV include this functionality? ho-hum.

    VB is my native language and ran the BindingList and comparer source through a c#-to-vb translator and it pretty much worked straight out of the box. Just have to look for

    Public Function Compare(ByVal x As T, ByVal y As T) As Integer and be certain it implements System.Collections.Generic.IComparer(Of T).Compare.

    Thanks again.

    Paul

  4. Works like a charm.

    Good job and thanks for sharing your knowledge.

  5. Perfect! a good solution for an incomplete topic of .NET.

    Gracias por todo. Saludos

  6. Nice job – I just dropped 2 files into my project, and it “just worked”!

    The only tweak I’ve done so far is to put the PropertyComparer class in the same file as the SortableBindingList – I like having a single file solution that can be shared with other projects.

    -Tom

  7. Wow – I have to confess that as I was mucking about with IQueryable results from Entity Framework queries I would have a lot of problems, but just converting the IQueryable to a list and wrapping it in the sortablelist worked immediately!

  8. Excellent!
    Two questions:
    1) Is there an obviously simple & efficient way to make the sort not mutate the original list? I want add/inserts to list to remain immutable; I just want them to *display* in different order according to the chosen sorting column.
    2) Do you think this would easily work on VirtualMode=true DGVs?

    Danka!

    Pv

  9. HI Tim,
    it was a great solution, it helped me very much in enabling sorting for DataGridView. ThankYou very much.

  10. Unfortunately, it does not support filtering. How to implement that? Help would be appreciated

  11. I’ve spent a few hours today on the web trying to figure how to enable the sort of the DataGridView.
    finally I have found this great article.
    Thanks for sharing!
    it really works great!

  12. Congratulations for this nice class.
    I have though a question:
    How can I use this when working with Entity Framework:

    public partial class FrmEF : Form
    {
    private academieEntities acaEnt;

    private void FrmEF_Load(object sender, EventArgs e)
    {
    myDataGridView.DataSource = myBindingSource;
    aca = new academieEntities();

    myBindingSource.DataSource = aca.acaEnt;

    }
    }
    My Gridview is populate but I don’t know how to use your class to make its column sortable.

    Thanks

  13. @Marcel you can do it like this:

    aca = new academiEntities();
    var academi = from a in aca.TableName select a;
    myBindingSource.DataSource = new SortableBindingList(academi.AsEnumerable());

  14. Tim, this is great. Thank you for doing it, and thank you for releasing it.

  15. Hi Tim,

    Thank you for your source code,

    my question:
    Is it possible to add a second parameter for sort operation in your SortableBindingList

    for example I have a SortableBindingList of Person class which has properties: Name (string) Surname (string) DateOfBirth (DateTime) and PersonID which is Integer. When I press to Name column header of dagatagridview which is bound to my SortableBindingList can I have my Persons to be sorted by Name + by PersonID (if names are equal then use PersonID for comparison).

    Will be very glad for your detailed answer!

    Thank you for your time! :razz:

  16. Hi Tim. Thank you very much for this solution. It’s really great when it is very easy to apply solution just in a few clicks. Thanks a lot!!!

    By the way, I needed to use sorting, but to keep the last row in the fixed position (to be always the last row).
    May be it will be useful for somebody. Happy codding.

    public class FixedLastRowSortedList : SortableBindingList {
    public FixedLastRowSortedList() {}

    public FixedLastRowSortedList(IEnumerable enumeration) : base(enumeration) {}

    protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) {
    var lastRow = Items.LastOrDefault();
    var itemsList = (List)GetListButLastRow();

    var propertyType = property.PropertyType;
    var comparer = GetOrCreateComparer(property, direction, propertyType);

    comparer.SetPropertyAndDirection(property, direction);
    itemsList.Sort(comparer);
    if(!Equals(lastRow, default(T)))
    itemsList.Add(lastRow);

    Items.Clear();
    foreach (var item in itemsList) {
    Items.Add(item);
    }

    PropertyDescriptor = property;
    ListSortDirection = direction;
    isSorted = true;

    OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
    }

    private IList GetListButLastRow() {
    return Items.Count > 1 ? Items.ToList().GetRange(0, Items.Count - 1) : Items;
    }
    }

  17. Thank you, this is a quick and easy solution to a big shortfall of the Entity Framework.

  18. Awesome for the most part, although I’m running into an issue. When a user clicks a column in which the data is bound to a child table’s column I get a null reference exception. “Object Reference Not Set To An Instance Of An Object.” (Using Linq) It’s something to do with not being able to handle the child table.

  19. Hello tim, thank you for sharing, Its works great,
    i have a problem,
    I have a datagridview which reload every second and I wanted to use this sorting mechanism. This works well but after reload the sorted data no longer exist, can you help me out.

    thank you

  20. Thanks for creating and sharing this, however I request some help, I have selected some Properties from my Database with the Select statement from the Entity Framework and it does not show any items, however when I put .ToList() at the end, it show the records but it is not sortable, please help. Thanks in Advance.
    database = new Database(connectionString);
    SortableBindingList programs = new SortableBindingList(database.Programs);
    dgvPrograms.DataSource = programs.Select(p => new
    {
    ID = p.ProgramId,
    p.ProgramDate,
    });

  21. Pingback: DataGridView not updating when bound BindingList is changed | BlogoSfera

  22. Was looking for a while for something like this. Dropped in the 2 files, changed my code a bit and that was it.

    Thanks again :)

    Nick

  23. Pingback: DataGridView not updating when bound BindingList is changed | Technology & Programming

Comments are closed.