New Control API for locking/unlocking drawing
Background and motivation
In WinForms development, there are scenarios where extensive updates to a control's state can cause flickering or performance degradation. For example, adding a large number of items to a list or making bulk changes to a grid may lead to unnecessary redraws. Developers often handle this by temporarily disabling the control's redrawing, applying changes, and re-enabling drawing afterward.
Currently, there is no built-in, intuitive mechanism in .NET WinForms to lock and unlock drawing for controls. This API proposal introduces methods to facilitate this, reducing flicker, improving performance, and providing a clean, structured way to manage drawing locks using a disposable pattern in C#.
API Proposal
public static void LockDrawing(this Control target);
public static void UnlockDrawing(this Control target);
public static DrawingLock UseDrawingLock(this Control target);
public ref struct DrawingLock : IDisposable
{
public DrawingLock(Control target);
public void Dispose();
}
API Usage
var control = someControl;
control.LockDrawing();
// Perform bulk updates
control.UnlockDrawing();
var control = someControl;
using (control.UseDrawingLock())
{
// Perform bulk updates
}
Alternative Designs
No response
Risks
- Error Handling - If UnlockDrawing is not called (e.g., due to an exception), the control may remain in a non-repainting state.
- Thread Safety - Accessing the Control.Handle from a thread other than the UI thread may cause runtime errors.
- Invalid Handles - Calling these methods on controls without a valid handle (e.g., before the control is created or after it is disposed) may fail.
Will this feature affect UI controls?
n/a
@elachlan doesn't the using handle the errors and if so, why is the first example not using it (pardon the pun)? An analyzer could enforce the use of using or at least not recommend lock unlock version in the documentation first.
If this is implemented is there a plan/desire to then us it in existing control implementation?
I provided both so there was an alternative. I wanted to highlight the risk. Its really up to the consumer to make sure they don't cause issues. The ref struct helps avoid the first risk.
It might make sense to implement it in Control and not via an extension method.
I use a "using ref struct" pattern like this a lot in my WinForms app (for several different things), and I also have manual methods because once in a while I need to make an async call inside the using block. Ref structs don't allow that of course, so the alternatives are either to make it a class (incurring an allocation), or have manual open/close methods. This is an uncommon case, but that's one possible reason for manual methods anyway, just throwing it out there.
Also, note that disabling redrawing on a Form (to be clear, we're talking about WM_SETREDRAW right?) will also cause input to "fall through" it to whatever's behind it, as I learned the hard way some time ago. It should only be done on controls within a window, and one can dock a panel inside the window, put all controls in it, and operate on that if one wants to set redraw on the "entire window".
@elachlan
- Do you, in fact, mean
WM_SETREDRAW? - Why is this better than enabling double buffering?
- What would this look like from within
OnPaint? - Would not create extension methods for
Controlas we own the implementation.
Do you, in fact, mean WM_SETREDRAW?
Yes
Why is this better than enabling double buffering?
Because I use it like this currently, if I don't I get flickering. Double Buffering does not help for whatever reason.
Try
LayoutControl1.LockDrawing()
LayoutControl1.BeginUpdate()
CustomLabelsTab = New CustomLabelsTab(DataSource, Settings)
CustomLabels_LayoutControlGroup.Add(New LayoutControlItem(LayoutControl1, CustomLabelsTab) With {.TextVisible = False})
Catch ex As Exception
Finally
LayoutControl1.EndUpdate()
LayoutControl1.UnlockDrawing()
End Try
What would this look like from within OnPaint?
I don't know.
Would not create extension methods for Control as we own the implementation.
Agreed, it should be directly on control.
@KlausLoeffelmann we'd love your opinion on this.
@KlausLoeffelmann could you please review this?
@KlausLoeffelmann Just following this up.
@merriemcgaw just checking in on this.
We have a meeting tomorrow. We'll get this on the agenda. @JeremyKuhne FYI.
@KlausLoeffelmann had made a similar suggestion a long time ago. He'll find the issue and we can work through approaches.
We're still discussing this internally. We will get back to you soon!
@merriemcgaw Just a gentle reminder on this one. Can we please get this for .NET 11?
@KlausLoeffelmann let's make sure this is on our planning radar!