The solution below came from pouring over the MSDN documentation and a little luck in some tangential threads here and on CodeProject.
The idea is to create a class, derived from DataGridViewCheckBoxCell, that includes a handler that is triggered along with the ContentClick. This may seem like lots of overhead for one DataGridView, but my application has many DataGridViews, so this code is reusable, thus saving time.
/// <summary>
/// DataGridView cell class for check box cells with a OnContentClick event handler.
/// </summary>
public class DataGridViewEventCheckBoxCell : DataGridViewCheckBoxCell
{
/// <summary>
/// Event handler for OnContentClick event.
/// </summary>
protected EventHandler<DataGridViewCellEventArgs> ContentClickEventHandler { get; set; }
/// <summary>
/// Empty constructor. Required. Used by Clone mechanism
/// </summary>
public DataGridViewEventCheckBoxCell()
: base()
{ }
/// <summary>
/// Pass through constructor for threeState parameter.
/// </summary>
/// <param name="threeState">True for three state check boxes, True, False, Indeterminate.</param>
public DataGridViewEventCheckBoxCell(bool threeState)
: base(threeState)
{ }
/// <summary>
/// Constructor to set the OnContentClick event handler.
/// Signature for handler should be (object sender, DataGridViewCellEventArgs e)
/// The sender will be the DataGridViewCell that is clicked.
/// </summary>
/// <param name="handler">Handler for OnContentClick event</param>
/// <param name="threeState">True for three state check boxes, True, False, Indeterminate.</param>
public DataGridViewEventCheckBoxCell(EventHandler<DataGridViewCellEventArgs> handler, bool threeState)
: base(threeState)
{
ContentClickEventHandler = handler;
}
/// <summary>
/// Clone method override. Required so CheckEventHandler property is cloned.
/// Individual DataGridViewCells are cloned from the DataGridViewColumn.CellTemplate
/// </summary>
/// <returns></returns>
public override object Clone()
{
DataGridViewEventCheckBoxCell clone = (DataGridViewEventCheckBoxCell)base.Clone();
clone.ContentClickEventHandler = ContentClickEventHandler;
return clone;
}
/// <summary>
/// Override implementing OnContentClick event propagation
/// </summary>
/// <param name="e">Event arg object, which contains row and column indexes.</param>
protected override void OnContentClick(DataGridViewCellEventArgs e)
{
base.OnContentClick(e);
if (ContentClickEventHandler != null)
ContentClickEventHandler(this, e);
}
/// <summary>
/// Override implementing OnContentDoubleClick event propagation
/// Required so fast clicks are handled properly.
/// </summary>
/// <param name="e">Event arg object, which contains row and column indexes.</param>
protected override void OnContentDoubleClick(DataGridViewCellEventArgs e)
{
base.OnContentDoubleClick(e);
if (ContentClickEventHandler != null)
ContentClickEventHandler(this, e);
}
}
Since I wanted to be able to reference this cell class from a column class, I also implemented a class derived from DataGridViewCheckBoxColumn:
/// <summary>
/// DataGridView column class for a check box column with cells that have an OnContentClick handler.
/// </summary>
public class DataGridViewEventCheckBoxColumn : DataGridViewCheckBoxColumn
{
/// <summary>
/// Empty constructor. Pass through to base constructor
/// </summary>
public DataGridViewEventCheckBoxColumn()
: base()
{ }
/// <summary>
/// Pass through to base constructor with threeState parameter
/// </summary>
/// <param name="threeState">True for three state check boxes, True, False, Indeterminate.</param>
public DataGridViewEventCheckBoxColumn(bool threeState)
: base(threeState)
{ }
/// <summary>
/// Constructor for setting the OnContentClick event handler for the cell template.
/// Note that the handler will be called for all clicks, even if the DataGridView is ReadOnly.
/// For the "new" state of the checkbox, use the EditedFormattedValue property of the cell.
/// </summary>
/// <param name="handler">Event handler for OnContentClick.</param>
/// <param name="threeState">True for three state check boxes, True, False, Indeterminate.</param>
public DataGridViewEventCheckBoxColumn(EventHandler<DataGridViewCellEventArgs> handler, bool threeState)
: base(threeState)
{
CellTemplate = new DataGridViewEventCheckBoxCell(handler, threeState);
}
}
Trimming away extraneous code, I'm using it like this:
public void AddCheckBoxColumn(DataGridView grid, EventHandler<DataGridViewCellEventArgs> handler, bool threeState)
{
grid.Columns.Add(new DataGridViewEventCheckBoxColumn(handler, threeState));
}
The column class could be eliminated, and it could be used as follows:
public void AddCheckBoxColumn(DataGridView grid, EventHandler<DataGridViewCellEventArgs> handler, bool threeState)
{
DataGridViewCheckBoxColumn column = new DataGridViewCheckBoxColumn(threeState);
column.CellTemplate = new DataGridViewEventCheckBoxCell(handler, threeState);
grid.Columns.Add(column);
}
Here's a sample event handler. The state of another column in the data grid is updated, based on the value of this column and conditionally some state information in the WasReleased property. The DataGridView's DataSource is a collection of Specimen objects, so each row's DataBoundItem is a Specimen. The Specimen class is application specific, but it has properties OnHold, displayed by this column; IsReleased, displayed by another column; and WasReleased.
public static void OnHoldCheckClick(object sender, DataGridViewCellEventArgs e)
{
if (sender is DataGridViewEventCheckBoxCell)
{
DataGridViewEventCheckBoxCell cell = sender as DataGridViewEventCheckBoxCell;
if (!cell.ReadOnly)
{
// The rows in the DataGridView are bound to Specimen objects
Specimen specimen = (Specimen)cell.OwningRow.DataBoundItem;
// Modify the underlying data source
if ((bool)cell.EditedFormattedValue)
specimen.IsReleased = false;
else if (specimen.WasReleased)
specimen.IsReleased = true;
// Then invalidate the cell in the other column to force it to redraw
DataGridViewCell releasedCell = cell.OwningRow.Cells["IsReleased"];
cell.DataGridView.InvalidateCell(releasedCell);
}
}
}
Good style would probably push some of the code in the event handler down into a method in the Specimen class.