It was pushed on me this week to modify a windows forms application I have been working on so that a DataGridView control was sortable. Since i’m a web guy, this turned out to be quite a challenge. There didn’t seem to be an immediately clear way to go through with this, short of dumping my data set (a generic List) into a dataset.
I proceeded to attempt to make collection object to implement BindingList, but a few stabs at that only demonstrated that I was going to spend far too much time making a one-time-use solution.
After consulting the Google, I came across a generic implementation of BindingList
I ended up modifying the class a small amount to remove the direction toggle logic in the ApplySortCore method which would sort a column in the opposite direction as requested under some circumstances, apply some better code commenting, and to apply strong types to some variables (as opposed to declaring them with ‘var’).
And now ..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | namespace ExampleIncorporated.BigProduct { using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using System.Reflection; /// <summary> /// Provides a generic method of storing objects in a bindable and sortable collection. /// </summary> /// <typeparam name="T">The type of object stored in the collection.</typeparam> public class SortableBindingList<T> : BindingList<T> { // reference to the list provided at the time of instantiation List<T> originalList; ListSortDirection sortDirection; PropertyDescriptor sortProperty; /// <summary> /// Used to store a local cache of generated methods used to sort the collection. /// </summary> /// <remarks> /// This in place so that the method used to sort a property will not need to be generated multiple times. /// </remarks> Action<SortableBindingList<T>, List<T>> populateBaseList = (a, b) => a.ResetItems(b); // a cache of functions that perform the sorting // for a given type, property, and sort direction static Dictionary<string, Func<List<T>, IEnumerable<T>>> cachedOrderByExpressions = new Dictionary<string, Func<List<T>, IEnumerable<T>>>(); /// <summary> /// Initializes a new instance of the <see cref="SortableBindingList<T>"/> class. /// </summary> public SortableBindingList() { originalList = new List<T>(); } /// <summary> /// Initializes a new instance of the <see cref="SortableBindingList<T>"/> class. /// </summary> /// <param name="enumerable">The enumerable.</param> public SortableBindingList(IEnumerable<T> enumerable) { originalList = enumerable.ToList(); populateBaseList(this, originalList); } /// <summary> /// Initializes a new instance of the <see cref="SortableBindingList<T>"/> class. /// </summary> /// <param name="list">The list.</param> public SortableBindingList(List<T> list) { originalList = list; populateBaseList(this, originalList); } /// <summary> /// Gets a value indicating whether the list is sorted. /// </summary> /// <value></value> /// <returns>true if the list is sorted; otherwise, false. The default is false.</returns> protected override bool IsSortedCore { get { return sortProperty != null; } } /// <summary> /// Sorts the items if overridden in a derived class; otherwise, throws a <see cref="T:System.NotSupportedException"/>. /// </summary> /// <param name="prop">A <see cref="T:System.ComponentModel.PropertyDescriptor"/> that specifies the property to sort on.</param> /// <param name="direction">One of the <see cref="T:System.ComponentModel.ListSortDirection"/> values.</param> /// <exception cref="T:System.NotSupportedException">Method is not overridden in a derived class. </exception> protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction) { // Update our local variables with the values given to us by the sorting control sortDirection = direction; sortProperty = prop; // Get the name of our method based on the sort direction string orderByMethodName = sortDirection == ListSortDirection.Ascending ? "OrderBy" : "OrderByDescending"; // Get the key used to cache the method based on the type GUID and method name string cacheKey = string.Concat( typeof(T).GUID, prop.Name, orderByMethodName); // If the method is not in our cache, generate the sort method if (!cachedOrderByExpressions.ContainsKey(cacheKey)) CreateOrderByMethod(prop, orderByMethodName, cacheKey); // Execute the sort method on the base list ResetItems(cachedOrderByExpressions[cacheKey](originalList).ToList()); // Re-bind the collection ResetBindings(); } /// <summary> /// Creates the order by method. /// </summary> /// <param name="prop">The property to sort by.</param> /// <param name="orderByMethodName">Name of the order by method.</param> /// <param name="cacheKey">The cache key.</param> private void CreateOrderByMethod(PropertyDescriptor prop, string orderByMethodName, string cacheKey) { ParameterExpression sourceParameter = Expression.Parameter(typeof(List<T>), "source"); ParameterExpression lambdaParameter = Expression.Parameter(typeof(T), "lambdaParameter"); // Get the type of the sort property PropertyInfo accesedMember = typeof(T).GetProperty(prop.Name); // Create an expression that selects the value of our sort property LambdaExpression propertySelectorLambda = Expression.Lambda(Expression.MakeMemberAccess(lambdaParameter, accesedMember), lambdaParameter); // Create our method MethodInfo orderByMethod = typeof(Enumerable).GetMethods() .Where(a => a.Name == orderByMethodName && a.GetParameters().Length == 2) .Single() .MakeGenericMethod(typeof(T), prop.PropertyType); // Create the linq order-by method var orderByExpression = Expression.Lambda<Func<List<T>, IEnumerable<T>>>( Expression.Call(orderByMethod, new Expression[] { sourceParameter, propertySelectorLambda }), sourceParameter); // Add the compiled method to the cache cachedOrderByExpressions.Add(cacheKey, orderByExpression.Compile()); } /// <summary> /// Removes any sort applied with <see cref="M:System.ComponentModel.BindingList`1.ApplySortCore(System.ComponentModel.PropertyDescriptor,System.ComponentModel.ListSortDirection)"/> if sorting is implemented in a derived class; otherwise, raises <see cref="T:System.NotSupportedException"/>. /// </summary> /// <exception cref="T:System.NotSupportedException">Method is not overridden in a derived class. </exception> protected override void RemoveSortCore() { ResetItems(originalList); } private void ResetItems(List<T> items) { base.ClearItems(); for (int i = 0; i < items.Count; i++) { base.InsertItem(i, items[i]); } } /// <summary> /// Gets a value indicating whether the list supports sorting. /// </summary> /// <value></value> /// <returns>true if the list supports sorting; otherwise, false. The default is false.</returns> protected override bool SupportsSortingCore { get { // indeed we do return true; } } /// <summary> /// Gets the direction the list is sorted. /// </summary> /// <value></value> /// <returns>One of the <see cref="T:System.ComponentModel.ListSortDirection"/> values. The default is <see cref="F:System.ComponentModel.ListSortDirection.Ascending"/>. </returns> protected override ListSortDirection SortDirectionCore { get { return sortDirection; } } /// <summary> /// Gets the property descriptor that is used for sorting the list if sorting is implemented in a derived class; otherwise, returns null. /// </summary> /// <value></value> /// <returns>The <see cref="T:System.ComponentModel.PropertyDescriptor"/> used for sorting the list.</returns> protected override PropertyDescriptor SortPropertyCore { get { return sortProperty; } } /// <summary> /// Raises the <see cref="E:System.ComponentModel.BindingList`1.ListChanged"/> event. /// </summary> /// <param name="e">A <see cref="T:System.ComponentModel.ListChangedEventArgs"/> that contains the event data.</param> protected override void OnListChanged(ListChangedEventArgs e) { originalList = base.Items.ToList(); } } } |
You can download the code here and find the original sample here.
Implementation was a snap:
List<myObject> MyList = DataLayer.GetMyList(); myDataGridView.DataSource = new SortableBindingList<myObject>(MyList); |






[...] http://www.ricky-dev.com/2009/10/making-your-generic-lists-sortable/ [...]