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.
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
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.
This works a treat. Fantastic code Tim!
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
Thank you for not only doing this but sharing.
Way to go!
Works like a charm.
Good job and thanks for sharing your knowledge.
Very nice work!
Perfect! a good solution for an incomplete topic of .NET.
Gracias por todo. Saludos
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
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!
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
Many thanks! Works perfectly.
Fantastic – thanks for this!!
HI Tim,
it was a great solution, it helped me very much in enabling sorting for DataGridView. ThankYou very much.
Unfortunately, it does not support filtering. How to implement that? Help would be appreciated
Genius your are my friend!!!
Congrats from Brazil!
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!
It helped me a lot, thanks
cheers
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
@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());
Thanks for the great work!
This is it, thank you!
Tim, this is great. Thank you for doing it, and thank you for releasing it.
Many thanks for this!
Thanks for the excellent work!
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!
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;
}
}