我们有一个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;
答案 0 :(得分:12)
正如Cosmin Prund所解释的那样,持续时间长的原因不是重新绘制的效果,而是VCL在控制运动中的重新调整要求。 (如果它确实应该花费的时间,那么你甚至可能需要来请求立即重绘。)
要暂时阻止重新调整以及所有检查和锚定工作,对齐设置和Z顺序,请使用DisableAlign
和EnableAlign
。通过直接调用它来调用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
比在单独的代码行中修改Left
和Top
更有效。
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上测试过。
无论如何,还有另一个非常重要的因素需要考虑。在运行时移动控件时,需要考虑所有控件的对齐和锚定的所有规则。这可能导致AlignControls
/ AlignControl
/ UpdateAnchorRules
次电话的雪崩。由于所有这些调用最终都需要相同的递归调用,因此调用的数量将是指数的(因此您观察到在TWinControl
上移动大量对象的速度很慢)。
最简单的解决方案是,正如大卫建议的那样,将所有内容放在面板上并将面板移动为一个面板。如果那是不可能的,并且您的所有控件实际上都是TWinControl
(即:它们有一个窗口句柄),您可以使用:
答案 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 正如大卫所说,移动父母比每个孩子快得多。