I was going over my to do list earlier this week and found a not that indicated I should follow up to the blog post I did on new VCL features in Delphi and C++Builder 11 (https://blog.marcocantu.com/blog/2021-vcl-features-rad11.html) about LockDrawing. In fact I wrote: "The base TWinControl class now offers LockDrawing and UnlockDrawing methods, to disable and control updating. This triggers the execution of the WM_SETREDRAW Windows message. This is probably worth its own blog post..." but than I totally forgot about this. So here is the blog post, with a few months delay.
Let's start from the Windows API. The message WM_SETREDRAW is send to a window to "allow changes in that window to be redrawn, or to prevent changes in that window from being redrawn." Any time you need to perform a set of operations, each requiring to repaint, you can put them on hold and repaint only at the end, avoiding flickering and making the process faster. It is of course important to remove the lock and than call a special function to force repaint (RedrawWindow).
All of this is properly encapsulated in two TWinControl methods added to the VCL library in RAD Studio 11, LockDrawing and UnlockDrawing, which send the WM_SETREDRAW message with proper parameters. They also implement a "lock count logic" so that if you lock the same control twice, you need to unlock it twice to resume painting. Additionally, the UnlockDrawing automatically calls the RedrawWindow API.
Having said this, which are the use cases? I wrote a demo showing two scenarios, and you can also check this nice blog post by Radek Cervinka, https://delphi.cz/post/TWinControlLockDrawing-a-TWinControlUnlockDrawing.aspx.
A first case in my demo is adding a bunch of elements to a list box. While Delphi traditionally offered and still has the ability to avoid repainting by "freezing" the string list update, it does make sense to lock the UI repaint instead. These are the two alternative coding styles:
var I: Integer; begin ListBox1.Items.BeginUpdate; try for I := 1 to 10000 do ListBox1.Items.Add('Line ' + I.ToString); finally ListBox1.Items.EndUpdate; end; var I: Integer; begin ListBox1.LockDrawing; try for I := 1 to 10000 do ListBox1.Items.Add('Line ' + I.ToString); finally ListBox1.UnlockDrawing; end;
In both cases the performance is much higher (and visible noticeable) than without one of the two solutions. Another scenario is that of a set of changes to a component causing a repaint. This is not trivial to simulate in a simple example, but one way to to cause flicker "on purpose" with code like this:
var I: Integer; begin Panel1.LockDrawing; try for I := 1 to 10000 do begin Panel1.Color := clBlue; Panel1.Repaint; Panel1.Color := clRed; Panel1.Repaint; end; finally Panel1.UnlockDrawing; end;
I know, this is fairly stupid, but you can see there is no flicker with the Lock/Unlock calls, while the effect look quite horrible without locking the UI. Not that I can easily render this in a screenshot, but here is the form at runtime anyway: