Delphi 如何临时停止绘制控件?
我们有一个win control对象,它将其客户机移动到其他坐标系。问题是,当有太多的子控件(例如500个控件)时,代码非常慢。 这一定是因为每次我设置Left和Top属性时,每个控件都被重新绘制。因此,我想告诉WinControl对象停止重新绘制,在将所有对象移动到新位置后,它可能会再次绘制(类似于备忘录和列表对象的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
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那不是真的左
和顶
已更改。@JavidTScrollBox
的滚动条调用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
子控件。当并没有任何显示时,我设置面板的标题。但是当我清空标题重画窗口时
会