0

I know the code below is C++-CLI, but it refers to C# in the same way

When I wanted to "combine" multiple rows in a DataGridView, I first tried to create a custom DataGridViewRow, but that had the disadvantage that it was almost impossible to implement other cell types than DataGridViewTextBoxCell.

My next solution was creating a custom DataGridView, which works really well now.
It overrides the functions

OnCellFormatting ()
OnCellPainting ()
OnRowsRemoved ()
OnSelectionChanged ()

The problem is, that often during debugging and sometimes during normal program execution, I get an exception from different places of internal .net code, which is called to draw the DataGridView or parts of it. Example:

Stack Trace:  
 bei System.Windows.Forms.DataGridViewTextBoxCell.PaintPrivate(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, Int32 rowIndex, DataGridViewElementStates cellState, Object formattedValue, String errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts, Boolean computeContentBounds, Boolean computeErrorIconBounds, Boolean paint)
 bei System.Windows.Forms.DataGridViewTextBoxCell.Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, Int32 rowIndex, DataGridViewElementStates cellState, Object value, Object formattedValue, String errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
 bei System.Windows.Forms.DataGridViewCell.PaintWork(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, Int32 rowIndex, DataGridViewElementStates cellState, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
 bei System.Windows.Forms.DataGridViewRow.PaintCells(Graphics graphics, Rectangle clipBounds, Rectangle rowBounds, Int32 rowIndex, DataGridViewElementStates rowState, Boolean isFirstDisplayedRow, Boolean isLastVisibleRow, DataGridViewPaintParts paintParts)
 bei System.Windows.Forms.DataGridViewRow.Paint(Graphics graphics, Rectangle clipBounds, Rectangle rowBounds, Int32 rowIndex, DataGridViewElementStates rowState, Boolean isFirstDisplayedRow, Boolean isLastVisibleRow)
 bei System.Windows.Forms.DataGridView.PaintRows(Graphics g, Rectangle boundingRect, Rectangle clipRect, Boolean singleHorizontalBorderAdded)
 bei System.Windows.Forms.DataGridView.PaintGrid(Graphics g, Rectangle gridBounds, Rectangle clipRect, Boolean singleVerticalBorderAdded, Boolean singleHorizontalBorderAdded)
 bei System.Windows.Forms.DataGridView.OnPaint(PaintEventArgs e)
 bei System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer)
 bei System.Windows.Forms.Control.WmPaint(Message& m)
 bei System.Windows.Forms.Control.WndProc(Message& m)
 bei System.Windows.Forms.DataGridView.WndProc(Message& m)
 bei System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
 bei System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
 bei System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

This is caused by deleting multiple rows at once from inside a non-UI thread. Actually, deleting them at once would be nice and would solve my problem, but that's impossible, because events are called after deletion of every single row. :-(
The exception is raised when a row is deleted while it's being painted, which of course cannot work. This happens just sometimes and not always, so it's definitely a race condition between deletion and paiting.

My question: How can I avoid that race condition since I don't have access to the code that causes it?

What I have done so far: before starting deletion of multiple rows, I set

m_bUpdateControl = true

In all 4 overridden functions, I have

if (m_bUpdateControl)
  return;

but that hasn't solved it yet.

What I have done next:

  • in OnCellFormatting() I use

    if (m_bUpdateControl) { i_oEventArgs->FormattingApplied = true; return; }

  • in OnCellPainting() I use

    if (m_bUpdateControl) { i_oEventArgs->Handled = true; return; }

But this still is not enough to safely avoid the race condition.
What else could I do?
Will this happen on a regular DGV as well?

The only remaining idea that I have is invoking a function for row deletion on the UI thread, so there will be only one thread operating on the DGV and the race condition is hopefully gone.

Edit:
I have tested with a regular DGV and always got an InvalidOperationException when deleting rows from a non-UI thread. Of course this makes sense, as cross-thread operations on UI objects are not possible. So I wondered why it was possible in my application.
I created a small test project and finally found the reason: The main application implements a DLL. As the vendor told me in a phone call, the Init routine of the main class of this DLL calls

Control.CheckForIllegalCrossThreadCalls = false;

which enables subsequent cross-thread operations. So if this check had not been disabled, I would have been forced to do the row deletion on the UI thread anyway, which most likely avoids the initial problem.

Tobias Knauss
  • 3,361
  • 1
  • 21
  • 45

1 Answers1

1

The combination of

  • invoking the row deletion on the UI thread
  • OnCellPainting() with
    if (m_bUpdateControl) { i_oEventArgs->Handled = true; return; }

seems to work.

At

void OnCellFormatting (...)
{
  DataGridView::OnCellFormatting (i_oEventArgs);

  if (IsConsecutiveCell (i_oEventArgs->RowIndex, i_oEventArgs->ColumnIndex))
  {
    i_oEventArgs->Value = String::Empty;
    i_oEventArgs->FormattingApplied = true;
  }
}

I had to remove all protection so that the DGV did not end up with cells with different types of content in the same column, which caused another error message and exception.

I still would like to know other solutions if existing.

Tobias Knauss
  • 3,361
  • 1
  • 21
  • 45