如何暂时停止控件的绘制?

时间:2013-01-25 11:19:07

标签: delphi delphi-xe

我们有一个win控制对象,可以将其客户端移动到其他一些坐标。问题是,当孩子太多时 - 例如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 个答案:

答案 0 :(得分:12)

正如Cosmin Prund所解释的那样,持续时间长的原因不是重新绘制的效果,而是VCL在控制运动中的重新调整要求。 (如果它确实应该花费的时间,那么你甚至可能需要来请求立即重绘。)

要暂时阻止重新调整以及所有检查和锚定工作,对齐设置和Z顺序,请使用DisableAlignEnableAlign。通过直接调用它来调用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;

答案 1 :(得分:6)

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

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

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

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

答案 2 :(得分:6)

你认为缓慢来自重新绘画控制的假设可能是正确的,但不是整个故事。处理移动控件的默认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; 

行为取决于控制! TControl(例如:TLabel)将根据德尔福的规则行事,但TWinControl取决于太多因素。一个简单的TPanel在循环之后才重新绘制,在我的机器上TButton 的情况下,只重绘背景,而TCheckBox是完全重新粉刷。在David的机器上,TButton也被完全重新绘制,证明这取决于许多因素。在TButton的情况下,最可能的因素是Windows版本:我在Windows 8上测试过,David在Windows 7上测试过。

AlignControl Avalanche

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

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

BeginDeferWindowPosDeferWindowPosEndDeferWindowPos

答案 3 :(得分:0)

为了加快速度,您应该在儿童活动期间将Visible属性WinControl设置为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;

BTW 正如大卫所说,移动父母比每个孩子快得多。