How to temporarily stop a control from being paint?

后端 未结 4 2133
伪装坚强ぢ
伪装坚强ぢ 2021-02-09 01:43

We have a win control object which moves its clients to some other coordiantes. The problem is, when there are too many children - for example 500 controls - the code is really

相关标签:
4条回答
  • 2021-02-09 02:09

    To speed up you should set the Visible property of you WinControl to False during child movement to avoid repainting.

    Together with SetBounds you will get the best from moving the 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 the parent is much faster than each child.

    0 讨论(0)
  • 2021-02-09 02:17

    I would put all the controls in a panel, and then move the panel rather than the controls. That way you perform the shift in a one single operation.

    If you would rather move the controls within their container then you can use TWinControl.ScrollBy.

    For what it is worth, it is more efficient to use SetBounds than to modify Left and Top in separate lines of code.

    SetBounds(Left+DX, Top+DY, Width, Height);
    
    0 讨论(0)
  • 2021-02-09 02:19

    Your assumption that the slowness comes from re-painting controls is probably true, but not the whole story. The default Delphi code that handles moving controls would delay painting until the next WM_PAINT message is received, and that would happen when the message queue is pumped, after you complete moving all the controls. Unfortunately there are a lot of things involved in this, that default behavior can be altered in many places, including Delphi and Windows itself. I've used the following code to test what happens when you move a 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; 
    

    The behaviour depends on the control! A TControl (example: TLabel) is going to behave according to Delphi's rules, but a TWinControl depends on too many factors. A simple TPanel is not repainted until after the loop, in the case of TButton on my machine only the background is re-painted, while a TCheckBox is fully repainted. On David's machine the TButton is also fully repainted, proving this depends on many factors. In the case of TButton the most likely factor is the Windows version: I tested on Windows 8, David tested on Windows 7.

    AlignControl Avalanche

    Anyhow, there's an other really important factor to be taken into account. When you move a control at runtime, all the rules for alignment and anchoring for all the controls need to be taken into account. This likely causes an avalanche of AlignControls / AlignControl / UpdateAnchorRules calls. Since all those calls end up requiring recursive invocations of the same, the number of calls will be exponential (hence your observation that moving lots of objects on a TWinControl is slow).

    The simplest solution is, as David suggests, placing everything on a Panel and moving the panel as one. If that's not possible, and all your controls are actually TWinControl (ie: they have a Window Handle), you could use:

    BeginDeferWindowPos, DeferWindowPos, EndDeferWindowPos

    0 讨论(0)
  • 2021-02-09 02:30

    As Cosmin Prund explains, the cause for the long duration is not an effect of repainting but of VCL's realignment requisites at control movement. (If it really should take as long as it does, then you might even need to request immediate repaints).

    To temporarily prevent realignment and all checks and work for anchors, align settings and Z-order, use DisableAlign and EnableAlign. And halve the count of calls to SetBounds by called 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;
    
    0 讨论(0)
提交回复
热议问题