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 useif (m_bUpdateControl) { i_oEventArgs->FormattingApplied = true; return; }
in
OnCellPainting()
I useif (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.