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 that uses more Linq then I understand to enable controls to automatically sort the dataset with no code-behind needed. Best of all, for performance, it will cache the generated code in a dictionary so that it doesn’t have to execute redundant reflection code (which can be costly, for sure).

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&lt;T&gt;"/> class.
        /// </summary>
        public SortableBindingList()
        {
            originalList = new List<T>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SortableBindingList&lt;T&gt;"/> 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&lt;T&gt;"/> 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 and see the original sample.

Implementation was a snap:

1
2
3
List<myObject> MyList = DataLayer.GetMyList();

myDataGridView.DataSource = new SortableBindingList<myObject>(MyList);