Thursday, October 21, 2010

DataGridView virtual mode with custom control race condition fix

This is for when you have a DataGridView in virtual mode in which you have both unbound and bound columns which depend on each other. It went wrong when editing the cells.
I found out that I got a race condition. I then checked the stacktrace:

- MyGridView.OnCellValueNeeded(System.Windows.Forms.DataGridViewCellValueEventArgs e)
- System.Windows.Forms.DataGridView.OnCellValueNeeded(int columnIndex, int rowIndex)
- System.Windows.Forms.DataGridViewCell.GetValue(int rowIndex)
- DataGridViewControlCell.GetValue(int rowIndex)
- System.Windows.Forms.DataGridView.OnCellValidating(ref System.Windows.Forms.DataGridViewCell dataGridViewCell, int columnIndex, int rowIndex, System.Windows.Forms.DataGridViewDataErrorContexts context)
- System.Windows.Forms.DataGridView.CommitEdit(ref System.Windows.Forms.DataGridViewCell dataGridViewCurrentCell, System.Windows.Forms.DataGridViewDataErrorContexts context, System.Windows.Forms.DataGridView.DataGridViewValidateCellInternal validateCell, bool fireCellLeave, bool fireCellEnter, bool fireRowLeave, bool fireRowEnter, bool fireLeave)
- System.Windows.Forms.DataGridView.CommitEdit(System.Windows.Forms.DataGridViewDataErrorContexts context, bool forCurrentCellChange, bool forCurrentRowChange)



So it tried getting the value for the cell that was currently being edited !
I fixed it in MyGridView (called with base.OnCellValueNeeded):

        protected override void OnCellValueNeeded(DataGridViewCellValueEventArgs e)
        {
            if (Columns[e.ColumnIndex] is DataGridViewControlColumn)
            {
                /*
                 * Special check to prevent race condition. Call this method first like this:
                 *    base.OnCellValueNeeded(e);
                 *    if (e.Value != null) return;
                 */
                if (Rows[e.RowIndex].Cells[e.ColumnIndex].IsInEditMode)        // editing control present?
                {
                    if ((EditingControl as TControl).EditingControlRowIndex == e.RowIndex)        // make sure it's the correct row
                    {
                        if ((EditingControl as TControl).EditingControlValueChanged)    // prevent re-use of old editing control
                        {
                            e.Value = (EditingControl as TControl).EditingControlFormattedValue;    // use value from editing control
                            return;
                        }
                    }
                }
            }
             base.OnCellValueNeeded(e);
        }

 Hopefully, you find this a bit useful.

No comments: