One of the features added to the VCL library in Delphi 11 is the ability to handle Windows WM_SETREDRAW message easily with two specific methods added to the TWinControl class: LockDrawing and UnlockDrawing
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 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.
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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:
PS: It is worth pointing out that one of the reason we added these methods was to use them in the RAD Studio IDE code, to improve the performance and reduce flickering.
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition
Would there be any advantage to using this instead of LockWindowUpdate(Self.Handle) for a Form?
LockDrawing/UnlockDrawing can be used when you have a form with a lot of controls and complex layout, and you want to change this layout. If you will lock drawing for all controls lying directly on the form, it will significantly speed up layout changes. But do not lock drawing of the form itself, because it will cause strange behavior off the mouse cursor (WinAPI limitation).
Like Mykola wrote “But do not lock drawing of the form itself”.
Is there a correct way to do it for an entire form?
There is not really a ‘good’ way to do it since it would block updates in general to the form and you then get screen artifacts if it’s dragged around – plus later versions of Windows can also get themselves into a state where they believe the app is no longer responsive and start showing error messages.
You can use the
LockWindowUpdate()
API call but for the reasons stated above you should really not use it and maybe even look at threads and the Synchronize calls to handle things properly. You can read a fairly lively discussion on this in this old Delphi groups post.This change in RAD Studio 11 seems to have broken some of our code that used pairs of WM_SETREDRAW messages.
After sending WM_SETREDRAW to a TPanel ( passing TRUE) to re-enable drawing followed by Refresh() the controls on the panel remain unpainted, only updating when the mouse was moved over a control.
After replacing SendMessage to send WM_SETREDRAW with LockDrawing/UnlockDrawing the code worked as previously.
It may be relevant that some of the controls on the TPanel were in a TFrame.
Tried it with a TTreeView:
1) For reference: Adding 10.000 Nodes -> 2400 ms
2) Using TTreeView.Items.BeginUpdate/Endupdate -> 1800 ms
3) Using TTreeView.Items.LockDrawing/UnLockDrawing -> 3000 ms
… so the new Option does not improve TTreeViews performance
There might some confusion there between locking the canvas for repainting and preventing the internals of the control from attempting to carry out refresh behavior and triggering of events.
If you use LockDrawing you are preventing an individual control from repainting the drawing elements (canvas). The control will still process other events such as reorganizing list elements and attempting to trigger “OnUpdate” type events.
If you use BeginUpdate you are actually saying to the control “please do not do anything with various unnecessary visual events”. If you combine BeginUpdate/EndUpdate and LockDrawing/UnlockDrawing then you are preventing the control from repainting and refreshing any other similar events. It’s a subtle difference but it is a difference.
For many controls the LockDrawing method will improve performance on its own, for some, as you’ve noticed, the best practice is to also use BeginUpdate/EndUpdate to ensure that the minimum activity takes place as you perform actions like adding nodes to the list or items to a listbox.
This does not seem to fully work with styled TScrollBox: the content does not redraw as expected, but the scrollbar does every time you add a control that changes the range (and the total add time is still much slower than when using with custom styles off).