I have a story about one instance in which The Project‘s management’s refusal to upgrade from an old version of .NET cost them valuable developer time on a feature that is trivialized in subsequent versions.
When showing all of the equipment of a certain type, there were certain statuses, like “In use” or “Discarded”, that were not to contribute to the equipment count of a room. And in order to keep the counts consistent with the full listing, it would make sense not to show such equipment in the listings. But then you had no easy way to change something from “In use” back to “Spare”.
The solution was to show them anyway in the listing, but highlight their rows to set them apart from the rest of the equipment. Since the natural Windows Forms control for this task is a DataGrid, I set out on figuring out how to highlight specific rows of a DataGrid in .NET 1.1.
The version number is key. Being restricted to .NET 1.1, I was unable to use a DataGridView, which is a customizable DataGrid. This cut me off from handling the CellFormatting event of DataGridView and implementing a rather painless solution to the problem.
After a long search, I found an article that describes the solution. Since the article is fairly detailed and contains lots of other examples, I will summarize how to color a specific row of a .NET 1.1 DataGrid (refer to the article itself for code examples):
- Define a new event with a new data type that will keep track of a cell’s row, column, background color, and font color. Make sure the new data type is passed by reference.
- Handle this event in the form that contains your DataGrid. Do not use the handles keyword. This way, you can re-use the handler and you aren’t tied to a specific number of columns.
In this handler, you provide the logic that determines which rows are colored. For us, we assume we have this info in a Hashtable somewhere. So we compare if the event’s row shows up in our Hashtable and is not selected. If so, set the background and font color properties of the event.
- Inherit from DataGridTextBoxColumn and override the seven-argument Paint method. This method will get called every time a cell is drawn on the screen.
- Now you fire the event you earlier defined, providing it with the current row and column, which you can get from inside the overridden Paint() and from Me.DataGridTableStyle.GridColumnStyles.IndexOf(Me).
- Finally, go back to your form and replace instances of DataGridTextBoxColumn with your derived column. Use the AddHandler statement to add the event handler to handle the derived column’s newly-defined event.
The event is handled in the form and its argument gets altered. After the RaiseEvent statement, you can inspect the argument to see if it was altered and set the appropriate brush color to pass to MyBase.Paint().
All of that to highlight a lousy row.
Well, fine, it might be an annoying exercise in polymorphism and event handling for me, but it did get the job done.
But there was a problem. The columns of this DataGrid had to be sortable. As it stood after the above steps, if you clicked on a column, the grid would sort based on that column, but there wouldn’t be a re-painting of the rows, so the wrong rows would be highlighted after a sort.
- Keep track of a need-to-repaint flag.
- Handle the DataGrid’s Paint event. Check the flag and re-paint if the flag is true. By “re-paint”, I mean iterate through each row, examine the correct column(s) value(s) that determine if a row needs to be highlighted, and add that row number to the Hashtable. Be sure to set the flag back to false or you’ll be painting forever.
- Handle the DataGrid’s OnClick event, not its OnMouseDown event. Perform a HitTest and see if the click happened on a column heading. If yes, set need-to-repaint to true.
Why not handle OnMouseDown? I was never fully sure if I correctly understood this MouseClick article:
Depressing a mouse button when the cursor is over a control typically raises the following series of events from the control:
1. MouseDown event.
2. Click event.
3. MouseClick event.
4. MouseUp event.
Mouse events occur in the following order:
3. MouseHover / MouseDown / MouseWheel
but I think it is because MouseDown occurs before MouseClick, so in MouseDown, let the re-sort happen with no re-paint. Then, in MouseClick, do a re-paint; this avoids re-painting too early.
OK, that is solved, but now whenever I have a substantial amount of data in a grid, there is a “flickering” effect whenever the grid is loaded or a column is sorted on.
Solution: in DataGrid’s Paint handler, hide the grid before doing the re-paint and then re-show it once finished.
OK, that is solved, but now, whenever a very substantial amount of data is in a grid, sorting by a column introduces a considerable multi-second delay before the grid is visible again.
So we’ve followed this to its logical conclusion: now we’ll have to implement a custom paging solution in order to side-step this performance problem. I never actually got around to implementing this feature, and possible solutions are the topic of a separate post.
So there you have it. Refusal to upgrade to a newer version of .NET, even though the platform itself is rather high-level, cost us substantial development and test time. Don’t underestimate the value of upgrades.