Ulrich Sprick
In this article I'll examine hosting a control in a datagrid column. One of the most simple control I can think of, is the button control. So I added a public button property to the column, and initialized it with a reference to a new button object in the constructor:
public class DataGridControlButtonColumn : DataGridColumnStyle
{
public System.Windows.Forms.Button Button = null;
// a reference to the hosted button
public DataGridControlButtonColumn()
{
// Default constructor. Creates a new button
this.Button = new System.Windows.Forms.Button();
this.Button.BackColor = System.Drawing.Color.Silver;
this.Button.Visible = false;
}
}
The Paint() method paints the column data just like the previous examples. The difference here is that overpainting the button is prevented for the cell that currently displays the button:
protected override void Paint( Graphics g, Rectangle bounds, CurrencyManager source, int rowNum, bool alignToRight )
{
// Helper.Trace( "Paint()", rowNum );
// no need to draw anything if the button is visible
if ( this.rowNum == rowNum && this.Button.Visible ) return;
object value = this.GetColumnValueAtRow( source, rowNum );
string text;
if ( value == null || value == DBNull.Value ) text = this.NullText;
else text = value.ToString();
// clear the background
Brush bgBrush = new SolidBrush( this.DataGridTableStyle.BackColor );
Brush fgBrush= new SolidBrush( this.DataGridTableStyle.ForeColor );
g.FillRectangle( bgBrush, bounds );
// draw the string
g.DrawString( text, this.DataGridTableStyle.DataGrid.Font, fgBrush, bounds );
}
The Edit() methods positions the button over the current cells, sets the button text from the datasource and makes it visible:
protected override void Edit( CurrencyManager source, int rowNum, Rectangle bounds,
boolreadOnly, string instantText, bool cellIsVisible )
{
// Start editing
Helper.Trace( "Edit()", rowNum, instantText );
// retrieve the current value
object value = this.GetColumnValueAtRow( source, rowNum );
if ( value == null ) this.Button.Text = this.NullText;
else this.Button.Text = value.ToString();
// move the button over the current cell and show
this.Button.Bounds = bounds;
this.Button.Visible = true;
this.Button.Focus();
this.rowNum = rowNum;
}
Abort() and Commit() do the opposite: They hide the button again.
protected override void Abort( int rowNum )
{
Helper.Trace( "Abort()", rowNum );
this.Button.Visible = false;
this.rowNum = -1;
}
protected override bool Commit( CurrencyManager dataSource, int rowNum )
{
Helper.Trace( "Commit()", rowNum );
this.Button.Visible = false;
this.rowNum = -1;
return true;
}
Abort() is called if Commit() returns false. But because Commit() always returns true, Abort() will never be called in this sample.
The rest of the code is not very exciting. GetMinimum/Preferred/Height/Size() return 10 by 20 pixels. As before, the grid ignores the minimum height returned from the column. You will find the code in the controlbutton.zip archive for download.

Figure 8: The Controlbutton Column
Let's have a look at the program log:
51:53.838 Constructor() 51:53.838 SetDataGridInColumn() System.Windows.Forms.DataGrid 51:53.858 SetDataGridInColumn() System.Windows.Forms.DataGrid 51:54.068 SetDataGridInColumn() System.Windows.Forms.DataGrid
The startup doesn't show any abnormal behaviour. After running through the constructor code, SetDataGridInColumn() ist called several times. As the docs suggest, the button control is added to the controls collection of the grid:
protected override void SetDataGridInColumn( DataGrid grid )
{
// add the button to the controls collection of the grid
Helper.Trace( "SetDataGridInColumn()", grid );
if ( this.Button.Parent != grid )
{
if ( this.Button.Parent != null ) this.Button.Parent.Controls.Remove( this.Button );
if ( grid != null ) grid.Controls.Add( this.Button );
}
// store a reference to the grid and attach event mouse handlers
this.grid = grid;
}
Now let's have a look at the trace log generated by (de)activation of our new button column. The button shows up with the caption text associated with that particular cell. The ReadOnly() property has not been overwritten(), so clicking around in the cells causes the Edit() and Commit() methods to be called in a certain pattern:
14:23.058 Edit() 0 14:27.995 Commit() 0 14:27.995 Commit() 0 14:33.473 Edit() 0 14:35.776 Commit() 0 14:35.786 Commit() 1 14:35.786 Edit() 1 14:42.977 Commit() 1 14:42.977 Commit() 1 14:42.977 Commit() 2
The number indicates the current row at the time of execution, so it is easy to follow the actions that happened:
You may have been wondering about the button1 in the form right above the grid. I added the button to track the focus when I type ESC on the City button in row 9 at 49:01:
48:55.755 Edit() 9 49:01.193 Commit() 9 49:01.193 Commit() 9 49:05.029 Commit() 9 49:05.029 Edit() 9 49:05.109 Commit() 9 49:05.109 Commit() 9
You can watch the focus rectangle on button1. A subsequent TAB at 49:05 puts the focus back on the button again - Edit() is called again after two additional calls to Commit(). Interesting: The TAB key is also processed by the grid, which causes an immediate activation of the next cell (that is the ID cell of row 10), which produces two more Commit()s. This calls for additional treatment, but I'll deal with this later.
After a close analysis of the call patterns I have put together this set of rules:
The rules are not complete, for example, I can produce the following seqence by clicking button1 and then the current cell in the grid:
32:50.444 Edit() 10 32:53.508 Commit() 10 33:04.184 Commit() 10 33:04.184 Edit() 10 33:04.184 Commit() 10 33:04.194 Edit() 10
For the last Commit()/Edit() sequence I have no reasonable explanation. Maybe it is according to the all present grid policy...
Nevertheless, the code in general is working: The button shows up with the first call to Edit(), and hides with the first run of Commit(). Subsequent calls() to Commit() do no harm - the hidden button is simply hidden several more times. This certainly ensures that the button is really hidden when it should be hidden. One never can be too careful!
Some other checks: What about scrolling up and down?
56:08.506 Commit() 1 56:08.506 Edit() 1 56:15.176 Commit() 1 56:15.176 Edit() 1 56:17.119 Commit() 1 56:17.119 Edit() 1 56:18.410 Commit() 1 56:18.410 Edit() 1 56:19.292 Commit() 1 56:19.302 Edit() 1
Scrolling, either line by line or by page, causes an edit to be started and terminated repeatedly. From the grid's point of view, an easy way to get the hosted controls in the columns at new positions. From my point of view: Lots of unnecessary overhead. In my opinion, a simple update of the y-coordinate of the controls in the grid's controls collection should be sufficient to do the job. And I really would like to do the scrolling with BitBlts, which would open up the ability for smooth scrolling and reduce the Paint() overhead at the same time. But I'm just a little unknown software engineer, and nobody asks me...
21:36.864 Commit() 1 21:36.974 Commit() 1 21:37.054 Commit() 1 21:37.134 Commit() 1 21:37.194 Commit() 1 21:37.264 Commit() 1 21:37.415 Commit() 1 21:37.485 Commit() 1 21:37.555 Commit() 1 21:37.625 Commit() 1 21:37.705 Commit() 1 21:37.795 Commit() 1
Note that Commit() causes the button to disappear.
Seems that I have to add some more rules...
The behaviour of our new column is ok, as long as we don't add a new row. Clicking in the City-Column of the add-new row of the grid creates a new data row. Now click in a different column of the same new row: The button does not disappear! How that? Even if you click in a different row, the button remains in the cell. Let's have a look in the trace output:
37:23.858 ConcedeFocus() 37:23.858 ConcedeFocus() 37:23.858 Edit() 91 39:00.857 ConcedeFocus() 39:00.857 ConcedeFocus()
The Commit() call after Edit() is missing. Instead, ConcedeFocus() is called. Twice, of course, according to the philosophy of the data grid implementation. Observation: The ConcedeFocus() calls at 37:23 before Edit() above appear only if the active column has not changed. In other words: If a different cell in the City column has been active before.
Rule: ConcedeFocus() is called whenever a cell looses focus, but only if the add-new row of the grid is involved.
What do the Docs say about the ConcedeFocus() method?
"Notifies a column that it must relinquish the focus to the control it is hosting."
Well, now I'm confused a bit. Further:
"Use this method to determine when a further action is required in a derived class. For example, this method is overridden by the DataGridTextBoxColumn to hide the DataGridTextBox."
I don't know what they mean by "further action", but the rest sounds better. And, with a view on the trace output, it makes sense. So let's give it a try:
protected override void ConcedeFocus()
{
Helper.Trace( "ConcedeFocus()" );
this.Button.Visible = false;
}
This change makes the column behave as expected. (If the application did always behave as expected, check that making the button invisible is commented out first)
In this article we have implemented a column style that host a button control. The control is shown in the cell in the Edit() method, and hidden again in Commit(), Abort() and ConcedeFocus(). The button is visible in the current cell only, so you must click twice to raise the button click event: First to activate the desired cell, and then again to click the button. This is different from the ImagebuttonColumn, where only one click is required to raise the click event. Of course this is possible to change the behaviour of the Controlbutton column in this way - you'll have to watch the mouse down event of the grid to distinguish a click with the mouse from a cell activation via keyboard.
Additionally, there is some unexpected behaviour with handling the TAB and Shift-TAB processing. I'll have a look at this problem in the next article.