问题
I am making a WinForms application which includes a form that uses a DataGridView
to handle simple data manipulation. To ensure accurate entry while mitigating clutter (read: without using DataGridViewComboBoxColumn
) I have a couple event handlers which temporarily turn a DataGridViewTextBoxCell
into an equivalent DataGridViewComboBoxCell
connected to values known to be "clean" when editing events are raised (typically when an editable cell is clicked):
private void OnCellEndEdit(object sender, DataGridViewCellEventArgs e)
{
//construct a textbox cell and set to current value
DataGridViewTextBoxCell cell = new DataGridViewTextBoxCell();
cell.Value = dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value;
//color row if invalid or modified
UpdateRowStyle(dataGridView.Rows[e.RowIndex]);
//switch to the new cell and redraw
dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex] = cell;
dataGridView.Refresh();
}
and
private void OnCellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
{
//construct a combobox cell, link to data, and set to current value
DataGridViewComboBoxCell cell = new DataGridViewComboBoxCell();
cell.DataSource = mDropDownValues[dataGridView.Columns[e.ColumnIndex].Name];
cell.Value = dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value;
//switch to the new cell and redraw
dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex] = cell;
dataGridView.Refresh();
}
Most of the time this works perfectly - the last cell edited reverts to a DataGridViewTextBoxCell
containing the newly selected data, and the cell selected for editing becomes a DataGridViewComboBoxCell
linked to the data specified in the mDropDownValues[]
dictionary. When editing a cell which has row and column indices that are equal, however, I run into trouble. The cell fails to change between the two types, throwing an exception on the dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex] = cell;
line in both event handlers (once when handling CellBeginEdit
and then again when handlingCellEndEdit
). The exception states
"Operation is not valid because it results in a reentrant call to the SetCurrentCellAddressCore function."
What is causing this error, and why does it only happen when editing the cells along the DataGridView
's diagonal? Is there some way I can achieve this functionality without throwing this exception?
回答1:
You are doing battle with this statement in SetCurrentCellAddressCore(), edited for readability:
// Allow the code to be re-entrant only as a result of
// underlying data changing.
if (this.dataGridViewOper[DATAGRIDVIEWOPER_inCurrentCellChange] &&
(this.dataConnection == null || !this.dataConnection.ProcessingListChangedEvent))
{
throw new InvalidOperationException(SR.GetString(SR.DataGridView_SetCurrentCellAddressCoreNotReentrant));
}
The DATAGRIDVIEWOPER_inCurrentCellChange flag is true. DataGridView has a lot of these kind of checks, no doubt added after extensive testing. It limits the number of things you can do while an event is raised, ensuring that the re-entrancy does not corrupt internal state of the control.
The universal solution to these kind of re-entrancy problems is to perform the operation later, after the event is raised and the internal state of the control is stable again. You can do so elegantly with the BeginInvoke() method, it runs when your program re-enters the dispatcher loop. Like this:
private void OnCellEndEdit(object sender, DataGridViewCellEventArgs e)
{
this.BeginInvoke(new Action(() => {
//construct a textbox cell and set to current value
DataGridViewTextBoxCell cell = new DataGridViewTextBoxCell();
cell.Value = dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value;
//color row if invalid or modified
UpdateRowStyle(dataGridView.Rows[e.RowIndex]);
//switch to the new cell and redraw
dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex] = cell;
dataGridView.Refresh();
}));
}
来源:https://stackoverflow.com/questions/27626158/reentrant-call-to-setcurrentcelladdresscore-in-event-handlers-only-where-cel