One aspect of DataGridViews that you can often find being discussed in a healthy and vigorous fashion on the internet is read-only cells, specifically the lack of TabStop = false functionality. It's very common to see dev's trying to force the selected cell to change when a Read-only one is focused and then they post on a board somewhere about a Reentrant Exception from SetCurrentCellAddressCore, the following code will stop the exception happening and find the next available cell (or control):
//a delegate is needed to avoid a circular loop when selecting a cell when in a cell selection event
private delegate void SetColumnAndRowOnGrid(DataGridView grid, int i, int o);
//can this cell be entered?
//***********************************************
//The Cell Enter Event Handler - First step in the code flow
//***********************************************
//***********************************************
//The Cell Enter Event Handler - First step in the code flow
//***********************************************
private void grid_CellEnter(object sender, DataGridViewCellEventArgs e)
{
//typesafe check
if (sender is DataGridView)
{
DataGridView grid = (DataGridView)sender;
if (grid.Rows[e.RowIndex].Cells[e.ColumnIndex].ReadOnly)
{
//this cell is readonly, find the next tabable cell
if (!setNextTabableCell(grid, e.ColumnIndex, e.RowIndex))
{
//or tab to the next control
setNextTabableControl();
}
}
}
else
{
throw new InvalidOperationException("this method can only be applied to controls of type DataGridView");
}
}
//***********************************************
//Find the next cell that we want to be selectable
//***********************************************
private bool setNextTabableCell(DataGridView grid, int nextColumn, int nextRow)
{
//keep selecting each next cell until one is found that isn't either readonly or invisable
do
{
//at the last column, move down a row and go the the first column
if (nextColumn == grid.Columns.Count - 1)
{
nextColumn = 0;
//at the last row and last column exit this method, no cell can be selected
if (nextRow == grid.Rows.Count - 1)
{
return false;
}
else
{
nextRow = Math.Min(grid.Rows.Count - 1, nextRow + 1);
}
}
else
{
nextColumn = Math.Min(grid.Columns.Count - 1, nextColumn + 1);
}
}
while (grid.Rows[nextRow].Cells[nextColumn].ReadOnly == true ||
grid.Rows[nextRow].Cells[nextColumn].Visible == false);
//a cell has been found that can be entered, use the delegate to select it
SetColumnAndRowOnGrid method = new SetColumnAndRowOnGrid(setGridCell);
grid.BeginInvoke(method, grid, nextColumn, nextRow);
//that's all I have to say about that
return true;
}
//***********************************************
// Method pointed to by the delegate
//***********************************************
//***********************************************
private void setGridCell(DataGridView grid, int columnIndex, int rowIndex)
{
grid.CurrentCell = grid.Rows[rowIndex].Cells[columnIndex];
grid.BeginEdit(true);
}
//***********************************************
// All the cells in the grid have been tried, none could be tabbed
// to so move onto the next control
//***********************************************
private void setNextTabableControl()
{
this.SelectNextControl(this, true, true, true, true);
}
Thank you for this code. Thomas
ReplyDelete