For anyone who is a C# programmer, or wants to be a C# programmer, or was a C# programmer and needs a refresher, this is the best reference I have ever read. Sean is practical, succinct, and humorous. I’ve linked to his first post of the series. He has other blogs that I will also be checking out.
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.
I have been working on a Windows application, TM1Compare. If you are a TM1 developer or administrator, I highly suggest you check it out. It can save you a lot of time and hassle. Click here to check it out and download the Lite version for free.
One of the features we have recently added is the ability to copy view data from one server to another. This can be done through TM1Compare without needing to export the view, copy-and-paste, or import the data through creating a complicated ETL process. TM1Compare handles all of this work in the background with a couple simple steps.
A challenge I came across while working on the view data feature was how to alert the user of important information without being annoying. TM1Compare allows the user to copy data on a single-view basis or in bulk. When copying a single view, if there is a warning we need to display to the user about that view, we display a message box and ask the user to make a choice whether to accept and continue or cancel. When the user is copying data in bulk, we didn’t want to repeatedly annoy the user with multiple message boxes, unless we could offer them the chance to make the same decision for all of the views at once. Several applications have similar functionality, such as a checkbox labeled “Don’t show this again” or “Apply to all…”. I was surprised to see that in the .NET Framework 4.5, not only is this not a standard option, but the MessageBox is a sealed class and cannot be extended to add it.
After reading several posts on StackOverflow.com, I found two options for creating the functionality I needed. Several people suggested creating a custom dialog window and displaying it as you would a normal window. I wanted my solution to have a look and feel just like the native Windows message box, which varies depending on the version of Windows the user is on, as well as the configurations they have set up in Windows, so I opted not to make my own window. The other option was a nifty trick posted by Code Project, building on Dino Esposito’s October 2002 and November 2002 MSDN articles to “hook into” to the native Windows message box and add the functionality directly into it. I decided this would work perfectly for my purposes, so I downloaded the sample code and modified it slightly to fit TM1Compare.
In Scanlon’s sample, the MessageBox wrote to the Windows registry. For my purposes, I didn’t want the user’s choice to be permanent, but to last just for the session they are in. I modified the MessageBox.Show() method to take an out parameter, which was then checked by the application and stored in a session variable.