Finishing the Column
That takes care of the cell part of the story. To finish creating the column, add a default constructor to the DataGridViewBarGraphColumn class, and modify the code so that it sets the column's
CellTemplate property, and forces the column to be read only:
[Visual Basic]
Public Class DataGridViewBarGraphColumn
Inherits DataGridViewColumn
Public Sub New()
Me.CellTemplate = _
New DataGridViewBarGraphCell()
Me.ReadOnly = True
End Sub
Public MaxValue As Long
Private needsRecalc As Boolean = True
Public Sub CalcMaxValue()
End Sub
End Class
[C#]
public class DataGridViewBarGraphColumn :
DataGridViewColumn
{
public DataGridViewBarGraphColumn()
{
this.CellTemplate =
new DataGridViewBarGraphCell();
this.ReadOnly = true;
}
public long MaxValue;
private bool needsRecalc = true;
public void CalcMaxValue(){}
}
Finally, you need to provide a way for the column to calculate its maximum value. Because the column really doesn't provide any simple way to hook itself into the parent control's set of events, the simple solution is to have a procedure (
CalcMaxValue) that each cell calls as it's being painted. Of course, you don't really need to calculate the maximum value for each cell; it's just that there's no other obvious and simple self-contained location in which to place code that you're guaranteed won't run until all the data has been loaded. (There may be other solutions to this particular problem, but the technique shown here is relatively benign, and works fine.)
Modify the
CalcMaxValue procedure so that it scans the values in the current column, tracking the maximum value. When the procedure is done, it sets the
NeedsRecalc field to false, so that subsequent calls to the procedure don't calculate the maximum value again:
[Visual Basic]
Public Sub CalcMaxValue()
If needsRecalc Then
Dim colIndex As Integer = Me.DisplayIndex
For rowIndex As Integer = 0 To _
Me.DataGridView.Rows.Count - 1
Dim row As DataGridViewRow = _
Me.DataGridView.Rows(rowIndex)
MaxValue = Math.Max(MaxValue, _
CLng(row.Cells(colIndex).Value))
Next
needsRecalc = False
End If
End Sub
[C#]
public void CalcMaxValue()
{
if (needsRecalc)
{
int colIndex = this.DisplayIndex;
for (int rowIndex = 0;
rowIndex < this.DataGridView.Rows.Count;
rowIndex++)
{
DataGridViewRow row =
this.DataGridView.Rows[rowIndex];
MaxValue = Math.Max(MaxValue,
Convert.ToInt64(row.Cells[colIndex].Value));
}
needsRecalc = false;
}
}
You could make the
needsRecalc field public, so that other callers could reset it. If, for example, you wanted to allow users to change values in the column, you would need to force a recalc of the maximum value. It's also possible that you could trap cell-changing events of the parent grid, and force a recalculation if a value in the corresponding column changed. I'll leave that as an exercise for the reader!
 | |
Figure 3. Column Type: Select your new class type, specifying the type of column you'd like in your grid. |
That's it—that's all you need. You've indicated that the template for cells in this column should use the DataGridViewBarGraphCell class, that this column should be read only, and added code that calculates the maximum value of the column.
Back in your form's Design view, edit the columns in the DataGridView control. For the
UnitsInStock column (or whatever numeric column you're using), select the DataGridViewBarGraphColumn type, as shown in
Figure 3. Click OK, then save and run your project. If all goes well, you should see a DataGridView that includes a bar graph column as shown in
Figure 1.
Of course, you're not limited to just this sort of customization. You can inherit from any of the various cell types, including columns that display Button, CheckBox, ComboBox, Image, or Link controls. If you inherit from the correct class, you'll find that most of the work has been done for you. In addition, each base class provides many protected methods you can override, giving you the capability to create exactly the cell type that you need. As in any such project, knowledge of the classes in the System.Drawing.Drawing2D namespace really comes in handy—take some time to investigate the classes you've seen in this example, if you haven't worked with them before. You'll certainly use them again.