How to temporarily stop a control from paint?

We have a victory management facility that moves its customers to some other coordinators. The problem is that when there are too many children - for example, 500 controls - the code is very slow. This should be due to the fact that each control is repainted every time I set the Left and Top property. So, I want to say that the object ceases to be a repainted object, and after moving all the objects to new positions, it can be drawn again (something like BeginUpdate for memos and list objects). How can i do this? Here is the code for moving objects; it's pretty simple:

 for I := 0 to Length(Objects) - 1 do begin with Objects[I].Client do begin Left := Left + DX; Top := Top + DY; end; end; 
+6
source share
4 answers

As Cosmin Prund explains, the reason for the long duration is not repainting, but the details of the VCL reorganization during the control movement. (If it really takes as long as it takes, then you even need to request an immediate copy).

To temporarily prevent reorganization and all checks and work for anchors, align settings and Z-order, use DisableAlign and EnableAlign . And reduce the call counter to SetBounds by calling it directly:

 procedure TForm1.FormCreate(Sender: TObject); var I: Integer; Control: TControl; begin for I := 0 to 499 do begin Control := TButton.Create(Self); Control.SetBounds((I mod 10) * 40, (I div 10) * 20, 40, 20); Control.Parent := Panel1; end; end; procedure TForm1.Button1Click(Sender: TObject); var I: Integer; C: TControl; begin // Disable Panel1 paint SendMessage(Panel1.Handle, WM_SETREDRAW, Integer(False), 0); Panel1.DisableAlign; try for I := 0 to Panel1.ControlCount - 1 do begin C := Panel1.Controls[I]; C.SetBounds(C.Left + 10, C.Top + 5, C.Width, C.Height); end; finally Panel1.EnableAlign; // Enable Panel1 paint SendMessage(Panel1.Handle, WM_SETREDRAW, Integer(True), 0); // Update client area RedrawWindow(Panel1.Handle, nil, 0, RDW_INVALIDATE or RDW_UPDATENOW or RDW_ALLCHILDREN); end; end; 
+11
source

I would put all the controls on the panel, and then move the panel, not the controls. This way you perform the shift in one operation.

If you prefer to move controls in your container, you can use TWinControl.ScrollBy .

Why use SetBounds more efficiently than changing Left and Top in separate lines of code.

 SetBounds(Left+DX, Top+DY, Width, Height); 
+6
source

Your assumption that slowness comes from re-paint controls is probably true, but not the whole story. By default, Delphi code that processes motion controls will delay painting until the next WM_PAINT message is received, and this will happen when the message queue is pumped after all controls have been moved. Unfortunately, there are many things in this that the default behavior can be changed in many places, including Delphi and Windows. I used the following code to check what happens when you move the control at runtime:

 var i: Integer; begin for i:=1 to 100 do begin Panel1.Left := Panel1.Left + 1; Sleep(10); // Simulate slow code. end; end; 

Behavior depends on management! A TControl (example: TLabel ) will behave according to Delphi rules, but TWinControl depends on too many factors. A simple TPanel not redrawn until after the cycle, in the case of TButton on my machine, only the background is repainted and the TCheckBox is completely repainted. On the machine, David TButton also completely repainted, and this depends on many factors. In the case of TButton most likely factor is the version of Windows: I tested on Windows 8, David tested on Windows 7.

AlignControl Avalanche

In any case, there is another important factor to consider. When you move a control at runtime, all alignment and binding rules for all controls must be taken into account. This can cause an avalanche of calls AlignControls / AlignControl / UpdateAnchorRules . Since all these calls ultimately require recursive calls of the same number, the number of calls will be exponential (therefore, your observation that moving a lot of objects to TWinControl is slow).

The simplest solution, as David suggests, is to place everything on a panel and move the panel as one. If this is not possible, and all of your controls are actually TWinControl ( TWinControl .: they have a Window Handle), you can use:

BeginDeferWindowPos , DeferWindowPos , EndDeferWindowPos

+6
source

To speed things up, you must set the Visible property for you WinControl to False while the child is moving, to avoid repainting.

Together with SetBounds you get the most out of moving child controls.

 procedure TForm1.MoveControls( AWinControl : TWinControl; ADX, ADY : Integer ); var LIdx : Integer; begin AWinControl.Visible := False; try for LIdx := 0 to Pred( AWinControl.ControlCount ) do with AWinControl.Controls[LIdx] do begin SetBounds( Left + ADX, Top + ADY, Width, Height ); end; finally AWinControl.Visible := True; end; end; 

BTW As David suggested, moving a parent is much faster than every child.

0
source

All Articles