DataGridView Custom SortingPosted: September 26, 2014
I have learned a couple quite interesting things (to me at least) over the past couple days:
1) When a .NET DataGridView is bound to a view and new unbound columns are dynamically added to it, it does not support native sorting on the unbound columns. This was not necessarily a shocker, however it led to the second interesting thing I learned.
2) Custom sorting in a .NET DataGridView can not only be performed via native properties of a datasource object, but can also be implemented on dynamic collections within the datasource object.
Here is the setup of the problem I had to solve:
A Windows Form containing a .NET DataGridView was bound to a custom view which supports custom sorting and filtering through generics. The generic type we used in the view had 5 predefined properties, and a “property bag” object which used a dictionary collection to store additional properties. The types of properties stored in the property bag varied based on the type of records being displayed. To keep data sparsity low both in the database and in the front-end, it made sense to leave these properties dynamic and not make them static members of the class. Over time, however, our users wanted to see specific property bag properties as if they were native properties to the class. These properties would have to be dynmically added to the form’s DataGridView and support sorting, filtering, and exporting just as if they were static members.
My first attempt at a solution was to dynamically add columns for the additional properties, set the DataGridView’s VirtualMode to true, and handle the CellValueNeeded event to populate the data for the added columns. This didn’t prove to be enough though because the DataGridView did not support automatic sorting for the dynamically added columns.
At this point, I took a detour to determine if dynamically building a standard DataView to bind the DataGridView to would be a better solution. It proved to be a reasonable solution that didn’t require much custom coding, but it caused a couple other issues. Some functions of the form were designed to use the custom view and its collection of generic objects. To work around this, I stored the generic objects in a new column of the DataView without exposing the new column to the DataGridView so I could still access them in the other functions of the code that needed them. This caused performance problems though, due to creating and maintaining two representations of the same data in memory. For this reasons, I turned back to the first solution and dug into what it would take to implement custom sorting on the added columns.
The MSDN article here was quite helpful in explaining how to do a fairly simple custom sort. However, what I was going to need required much more complexity. Not only were the added properties unavailable at design time, but the way our custom view performed sorting was through a delegate function. I had already learned how to add the properties dynamically, but the delegate function would also need to be created dynamically.
Luckily, I found that this particular problem had largely already been solved by one of my coworkers in another area of the application. I copied his code and modified it to fit my situation. To keep the delegate function tied to the column it belonged to, I stored it in the column’s “Tag” property.
When I got the custom sort implemented and started testing it, I ran into an unexpected behavior. Apparently, when creating a delegate dynamically, any part of the delegate that uses a reference variable will store the value of the variable in the function. In my case, this was a problem because I used a reference variable for the name of the column within a foreach loop, which changed the variable’s value with each iteration. The end result was that each time the delegate function was called, it sorted the last added column instead of the column that needed to be sorted. The final step in implementing the custom sort (correctly) was to replace the reference variable with an instance variable. I used the column object’s “DataPropertyName” property instead, and then the sort worked like a charm.
In the end, I learned that the DataGridView is much more flexible than I ever knew. Not only does it offer several choices for presenting and working with data using standard DataTables and DataViews, but it was also designed to work very well with custom implementations. Kudos to the Microsoft engineers that designed it to work so well! On the other hand, I’m not quite as pleased that delegate functions can change their behavior when combined with reference variables.