8

我们有一个获胜控制对象,它将其客户移动到其他一些坐标。问题是,当孩子太多时——例如 500 个控件——代码真的很慢。这一定是因为每次我设置 Left 和 Top 属性时都会重新绘制每个控件。所以,我想告诉 WinControl 对象停止被重新绘制,并且在将所有对象移动到它们的新位置之后,它可能会被再次绘制(类似于BeginUpdate备忘录和列表对象)。我怎样才能做到这一点?这是移动对象的代码;这很简单:

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

4 回答 4

14

正如Cosmin Prund解释的那样,持续时间长的原因不是重新绘制的效果,而是 VCL 在控制移动时的重新调整要求。(如果它真的需要花这么长的时间,那么您甚至可能需要请求立即重绘)。

要暂时防止重新对齐以及所有检查和锚点工作,对齐设置和 Z 顺序,请使用DisableAlignEnableAlignSetBounds并通过直接调用将调用次数减半:

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;
于 2013-01-25T12:46:49.580 回答
7

您认为缓慢来自重新绘制控件的假设可能是正确的,但不是全部。处理移动控件的默认 Delphi 代码将延迟绘制,直到WM_PAINT收到下一条消息,并且在您完成移动所有控件之后,当消息队列被抽出时,就会发生这种情况。不幸的是,这涉及到很多事情,默认行为可以在很多地方改变,包括 Delphi 和 Windows 本身。我使用以下代码来测试在运行时移动控件时会发生什么:

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

行为取决于控制!A TControl(example: TLabel) 将按照 Delphi 的规则运行,但 aTWinControl取决于太多因素。A simpleTPanel直到循环之后才重新绘制,TButton 在我的机器上,只有背景被重新绘制,而 aTCheckBox被完全重新绘制。在大卫的机器TButton上也完全重新粉刷,证明这取决于许多因素。最TButton可能的因素是 Windows 版本:我在 Windows 8 上进行了测试,David 在 Windows 7 上进行了测试。

AlignControl 雪崩

无论如何,还有一个非常重要的因素需要考虑。在运行时移动控件时,需要考虑所有控件的对齐和锚定规则。AlignControls这可能会导致/ AlignControl/UpdateAnchorRules呼叫雪崩。由于所有这些调用最终都需要对相同的递归调用,因此调用的数量将是指数级的(因此您观察到在 a 上移动大量对象TWinControl很慢)。

正如大卫建议的那样,最简单的解决方案是将所有内容放在面板上并将面板作为一个整体移动。如果这是不可能的,并且您的所有控件实际上都是TWinControl(即:它们有一个窗口句柄),您可以使用:

BeginDeferWindowPos , DeferWindowPos , EndDeferWindowPos

于 2013-01-25T11:40:58.723 回答
6

我会将所有控件放在一个面板中,然后移动面板而不是控件。这样,您就可以在一次操作中执行移位。

如果您希望在其容器中移动控件,则可以使用TWinControl.ScrollBy.

对于它的价值,使用它SetBounds比修改LeftTop单独的代码行更有效。

SetBounds(Left+DX, Top+DY, Width, Height);
于 2013-01-25T11:21:24.323 回答
0

为了加快速度,您应该在孩子移动期间将您的属性设置为以Visible避免重新绘制。WinControlFalse

SetBounds您一起从移动子控件中获得最佳效果。

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;

顺便说一句,正如大卫所建议的,移动父母比每个孩子都要快得多。

于 2013-01-25T14:11:24.727 回答