… or why simply replacing LockWindowUpdate by WM_SETREDRAW is not that straightforward.
As you know, when you want to avoid flickering or multiple partial redraws of your Forms, during a flurry of updates for instance, even though it is very tempting, you must not use LockWindowUpdate. The Windows documentation has been updated from the days when it practically enticed people into using this API call for this wrong purpose and Raymond Chen has explained in detail why:
What does LockWindowUpdate do?
With what operations is LockWindowUpdate meant to be used?
With what operations is LockWindowUpdate not meant to be used?
Final remarks on LockWindowUpdate
Now, just replacing
SendMessage(MyForm.Handle, WM_SETREDRAW, 0, 0) on the whole Form is not the solution either. Even though I’ve seen it recommended here or there.
Oh, it does a good job at preventing any painting on the Form, and if drawing is re-enabled quickly enough, there is little chance to cause the problem we’ll see below.
Simulate a long calculation that coincides with some visible UI changes.
Create a new VCL Forms application and put 2 Buttons on the Main Form.
In the 1st ButtonClick handler, paste the following code:
procedure TForm1.Button1Click(Sender: TObject); begin Button1.Width := Button1.Width*2; Repaint; sleep(2000); Button1.Width := Button1.Width div 2; Repaint; end;
If you click the button, its size changes, the application stays unresponsive for 2 s (lengthy processing without a background thread) then everything is back to normal. If you click on the Form while it is “frozen” nothing happens unless you have an onClick event, or try to move the Form for instance, and in that case the event action is executed after the Form is unfrozen, when message processing can resume and handle the backlog.
Now, if you don’t want the UI changes to be visible.
You can wrap all that code with WM_SETREDRAW (like you’d be tempted to do with LockWindoUpdate):
procedure TForm1.Button1Click(Sender: TObject); begin SendMessage(Handle, WM_SETREDRAW, Ord(False), 0); try Button1.Width := Button1.Width*2; Repaint; sleep(2000); Button1.Width := Button1.Width div 2; Repaint; finally SendMessage(Handle, WM_SETREDRAW, Ord(True), 0); end; end;
Try it again. Seems good, the button size does not appear to change. But, wait!
Try to click anywhere on the form while it is “busy”.
Oops! Clicked through it, like it did not even exist!
It’s even more obvious when the form is above the Delphi editor: as soon as you click the button, the cursor changes from the regular Arrow pointer to the text IBeam.
Try with Notepad.
In case you’d believe it is some defect special to the VCL Forms, you can try and paste the following code in the 2nd Button OnClick handler. It opens Notepad for a new text document, prevents redrawing on it for 3s then re-enables it.
Note: If the window title is different (non English Windows…), you may have to adapt the 2nd FindWindow parameter or use an empty string.
procedure TForm1.Button2Click(Sender: TObject); var h: THandle; begin ShellExecute(Handle, nil, 'Notepad.exe', '','', SW_SHOWNORMAL); sleep(100); // you may have to change the default title in non English Win7 or put '' h := FindWindow('Notepad', 'Untitled - Notepad'); if IsWindow(h) then begin SendMessage(h, WM_SETREDRAW, Ord(False), 0); try sleep(3000); finally SendMessage(h, WM_SETREDRAW, Ord(True), 0); end; end; end;
Try to click anywhere on the Notepad window when it is “locked”, you’ll get right through it just as well!
What if you need to prevent drawing on the Form while doing some processing?
As it is very rare that the UI controls subject to unwanted drawing are directly placed on the Form, the easiest solution is to prevent drawing for the utmost parent, very often a Panel or a Tab/PageControl (the ClientWindow would not work).
And if you don’t have a master parent, you can always insert a Panel with alClient as a main container between the Form and the rest of the controls.
Useful tip: Contrary to DisableControls/EnableControls for the DataSets, WM_SETREDRAW does not care about nested calls, it does not matter how many times you call WM_SETREDRAW False (although avoiding sending unnecessary messages flying around is always desirable). You just have to guarantee that you’ll call WM_SETREDRAW True when you are done with your updates.
Why do we have this strange behavior?
Practically, WM_SETREDRAW default behavior is for the Window receiving it to disappear, but without causing the screen to be refreshed to show what’s behind. We are left with a “ghost” painting of the current Window, and is truly very static indeed.
It has been explained, sort of, by Raymond Chen in a very recent post:
There’s a default implementation for WM_SETREDRAW, but you might be able to do better
Excellent post, thanks.
“the easiest solution is to prevent drawing for the utmost parent, very often a Panel” What method do you suggest to prevent drawing on a panel in D2010?