【问题标题】:How to temporarily stop a control from being painted?如何暂时停止绘制控件?
【发布时间】:2013-01-09 08:27:39
【问题描述】:

我们有一个获胜控制对象,它将其客户移动到其他一些坐标。问题是,当孩子太多时——例如 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;

【问题讨论】:

  • @kobik 不,您不应该为此调用该 API。它的用途完全不同。
  • @kobik:不!来自应该知道的人:blogs.msdn.com/b/oldnewthing/archive/2007/02/22/1742084.aspx
  • @UliGerhardt 和 David,我知道他知道,而且我知道该功能基本上用于拖动操作或屏幕捕获/间谍实用程序,但简单的事实是无论如何它似乎总是有效。 Raymond 建议的WM_SETREDRAW 过去似乎对我不起作用,所以我不使用它。而DisableAlign/EnableAlign 并没有消除闪烁,但确实可以让事情变得更快。
  • @UliGerhardt,试试WM_SETREDRAW(在Panel1.Handle上)和NGLN's code,你就会明白我的意思了。按钮冻结并且不会重新定位(而 LockWindowUpdate 工作得很好)。也许ScrollWindowEx 更适合这种情况。那要看真实的代码和用法了……

标签: delphi delphi-xe


【解决方案1】:

正如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;

【讨论】:

  • 单次调用 ScrollBy 不是更简单吗?
  • @David 是的,那会。根据容器控件的类型,它可能还需要随后调用Invalidate,但它肯定会简单得多。虽然,如果只需要移动容器内所有控件的选择(请注意,OP 只移动他的 Objects 数组中的项目),那么我认为这个答案最合适。
  • 我使用了您的部分代码来更新 TFlowPanel 子控件。当没有可显示的内容时,我设置了面板的标题。但是当我清空标题RedrawWindow 时,不会删除之前绘制的文本,我需要调用一个额外的Invalidate。我应该将另一个标志传递给RedrawWindow 来覆盖它吗?
  • @saastn 我不熟悉TFlowPanel。您当然可以尝试其他标志。你也可以试试my first edit
【解决方案2】:

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

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

不管怎样,使用SetBounds 比在单独的代码行中修改LeftTop 更有效。

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

【讨论】:

  • 效果相同。滚动框内的控件不会相对于滚动框移动。他们的LeftTop 在你滚动时不会改变。
  • @David 这不是真的; LeftTop 已更改。
  • @Javid TScrollBox 的滚动条调用TWinControl.ScrollBy,它使用ScrollWindow,并在孩子不是@987654336 的情况下更改LeftTop 属性后面的私有字段@.
  • 所以只要调用 ScrollBy 就可以了。
  • 如果您给我们一个演示程序,我们可以对其进行优化。
【解决方案3】:

您关于缓慢来自重新绘制控件的假设可能是正确的,但不是全部。处理移动控件的默认 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)将按照 Delphi 的规则运行,但 TWinControl 取决于太多因素。一个简单的TPanel 直到循环之后才会重新绘制,对于TButton 在我的机器上,只有背景被重新绘制,而TCheckBox 被完全重新绘制。在 David 的机器上,TButton 也完全重新粉刷,证明这取决于许多因素。在TButton 的情况下,最可能的因素是 Windows 版本:我在 Windows 8 上测试,David 在 Windows 7 上测试。

AlignControl 雪崩

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

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

BeginDeferWindowPosDeferWindowPosEndDeferWindowPos

【讨论】:

    【解决方案4】:

    为了加快速度,您应该在孩子移动期间将WinControlVisible 属性设置为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;
    

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

    【讨论】:

      猜你喜欢
      • 2010-10-04
      • 1970-01-01
      • 2013-09-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-28
      • 1970-01-01
      相关资源
      最近更新 更多