Delphi 如何临时停止绘制控件?

Delphi 如何临时停止绘制控件?,delphi,delphi-xe,Delphi,Delphi Xe,我们有一个win control对象,它将其客户机移动到其他坐标系。问题是,当有太多的子控件(例如500个控件)时,代码非常慢。 这一定是因为每次我设置Left和Top属性时,每个控件都被重新绘制。因此,我想告诉WinControl对象停止重新绘制,在将所有对象移动到新位置后,它可能会再次绘制(类似于备忘录和列表对象的BeginUpdate)。我该怎么做? 这是移动物体的代码;很简单: for I := 0 to Length(Objects) - 1 do begin with Objec

我们有一个win control对象,它将其客户机移动到其他坐标系。问题是,当有太多的子控件(例如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;

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

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

就其价值而言,使用
SetBounds
比在单独的代码行中修改
Left
Top
更有效

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

您认为缓慢来自重新绘制控件的假设可能是正确的,但并非全部。处理移动控件的默认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; 
行为取决于控制!
t控件
(示例:
TLabel
)将根据Delphi的规则进行操作,但是
TWinControl
取决于太多的因素。简单的
t面板
在循环后才重新喷漆,如果我的机器上有
t按钮
,则只重新喷漆背景,而
t复选框
则完全重新喷漆。在David的机器上,
t按钮
也完全重新喷漆,这取决于许多因素。在
t按钮的情况下
最有可能的因素是Windows版本:我在Windows 8上测试,David在Windows 7上测试

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

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

正如所解释的,持续时间长的原因不是重新喷漆的影响,而是控制移动时VCL的重新校准要求。(如果真的需要这么长的时间,那么您甚至可能需要请求立即重新绘制)

若要临时防止重新对齐和所有检查,并对锚点执行操作,请对齐设置和Z顺序,使用和。通过直接调用,将调用
SetBounds
的次数减半:

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;

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

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;

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

同样有效。滚动框内的控件不会相对于滚动框移动。他们的
在你滚动时不会改变。@David那不是真的
已更改。@Javid
TScrollBox
的滚动条调用
TWinControl。ScrollBy
使用,并更改
Left
Top
属性后面的私有字段,以防childs不是
TWinControl
。因此,如果这符合您的要求,只需调用ScrollBy。如果您给了我们一个演示程序,我们可以对其进行优化。?@kobik不,您不应该为此调用该API。它的用途完全不同。@kobik:不!从一个应该知道的人那里:@UliGerhardt和David,我知道他知道,我知道这个函数基本上用于拖动操作或屏幕捕获/间谍实用程序,但简单的事实是,不管发生什么,它似乎总是有效的
WM_SETREDRAW
正如Raymond建议的那样,过去似乎对我不起作用,所以我不使用它。而
DisableAlign
/
enaablealign
并不能消除闪烁,但一定能让事情进展得更快。@UliGerhardt,试试
WM_SETREDRAW
(在Panel1.Handle上),你就会明白我的意思了。按钮冻结,不会重新定位(而LockWindowUpdate工作正常)。也许
ScrollWindowEx
更适合这种情况。这取决于真正的代码和用法……对ScrollBy进行一次调用不是更容易吗?@David是的,那会。根据容器控件的类型,它可能还需要后续调用
Invalidate
,但肯定会简单得多。虽然,如果只需要移动容器中所有控件的一部分(请注意,OP只移动其
对象
数组中的项),那么我认为这个答案最合适。我使用了部分代码来更新
TFlowPanel
子控件。当并没有任何显示时,我设置面板的标题。但是当我清空标题
重画窗口时