Delphi Win32:加速动态创建的控件(Parent属性)

9
我们有一个由几个框架构成的GUI,其内容是动态生成的。每个框架创建面板、标签、编辑框、下拉框等作为输入字段。这个功能非常好用,我们计划让每个框架在单独的线程中构建其内容,但有一个大问题:速度比较慢!创建控件不需要时间,但设置Parent属性却非常耗时。我尝试了多种方法来加快进程,但没有任何帮助。我尝试了Enabled=False、Visible=False、DisableAlign、LockWindowUpdate、WM_SETREDRAW等方法,但似乎都无法影响控件Parent属性的耗时过程。即使使用线程,由于VCL函数必须在同步中调用,也会花费一些时间。是否有其他方法可以加快控件的创建和显示速度呢?顺祝商祺,Magnus。
编辑:GUI中没有数据组件或任何触发事件。我只是创建并显示控件。通过定时器,我确定控件父级(AControl.Parent := AOwner)分配是耗时的部分。
编辑2:正如下面的答案所示,速度问题不在于设置父级,而在于控件的绘制。当我测试时,容器是可见的,并且设置父级会立即绘制控件。
编辑3:我们动态GUI的另一个耗时部分是将项目分配给下拉框。ComboBox.Items.Assign(DataItems),其中DataItems最多只有三到六个项目。
谢谢大家抽出时间来帮助我!
8个回答

5
不要尝试使用多线程创建控件或者处理VCL。这样做不会提高速度,更重要的是,在VCL中这是完全不可取的。
编辑:您应该阅读StackOverflow上处理VCL和多线程的其他问题和答案,简单来说:VCL不支持多线程,所有对控件的访问都必须在主线程的上下文中进行。因此,当使用多个线程时,您几乎需要将所有内容包装在Synchronize()调用中,这实际上会序列化所有线程并进一步减慢速度。
最好的方法是重新构造UI,使其不需要一次性创建所有内容。只有在第一次显示时才按需创建所有帧。
编辑2:这里有一些测试代码,展示了设置Parent属性不是真正的问题,但创建所有控件(及其所涉及的所有消息处理)可能是问题所在。
procedure TForm1.Button1Click(Sender: TObject);
var
  i, j, x, y: integer;
  Edit: TEdit;
  Ticks: LongWord;
begin
  Visible := FALSE;
  DestroyHandle;

  try
    for i := 1 to 20 do begin
      y := 20 + i * 25;
      for j := 1 to 10 do begin
        x := (j - 1) * 100;

        Edit := TEdit.Create(Self);
        Edit.SetBounds(x, y, 98, 23);
        Edit.Parent := Self;
      end;
    end;
  finally
    Ticks := timeGetTime;
    Visible := TRUE;
    Caption := IntToStr(timeGetTime - Ticks);
  end;
end;

该代码动态创建了200个TEdit控件,但是在释放父窗体句柄后创建所有这些控件并设置它们的属性需要几十毫秒。但最终显示窗体(将创建所有窗口)需要几百毫秒。因为这只能在主线程中完成,所以我怀疑使用多个线程不会有帮助。

多线程的目的就是让用户在用户界面的某一部分继续工作,同时内容正在其他部分(框架)加载。我知道这样做可能不会更快,但至少用户不必等待它完成。 - Magnus Rosendahl
谢谢,我现在先放下线程,不过我仍然认为这是一个好主意。 :-) - Magnus Rosendahl

4
你在设置DisableAlign时针对哪些控件?尝试在每个可以容纳子控件的控件上(例如面板)进行DisableAlign。我曾经看到,在动态构建表单时,DisableAlign会导致巨大的加速效果。
编辑:进一步思考后,我的答案部分是推测性的。我不知道在控件树的根上设置DisableAlign的效果是否会传递给它的子元素。我假设它不会,但也许会。我需要查看VCL代码。(然而有关加速的部分是正确的。)

1
谢谢,我只是禁用了框架本身。 - Magnus Rosendahl
1
这真的很棒,我通过在父面板上设置DisableAlign,成功将表单构建的性能提高了30%。只是最后别忘了调用EnableAlign。 - A. Markóczy

4
另一个猜测:尝试使用 Visible := false 创建容器(表单、面板或框架)。然后将所有动态创建的控件附加到该容器上,然后设置 Visible := true。

2
我不确定这种方法是否可行,但您可以尝试将表单创建为.dfm文本格式,然后使用ObjectTextToBinary函数将.dfm直接加载到表单中。这可能有效,值得探究。

有趣的想法,但对于如此简单的任务来说,这似乎是一条漫长的路。创建不是问题,但控件的绘制/对齐是问题所在,我猜无论框架是从二进制流创建还是其他方式,这个问题都是一样的。但我可能会尝试一下。 - Magnus Rosendahl
@Magnus:你确实应该尝试一下,从TWinControl.InsertControl()的实现过程来看,当csReading处于活动状态时,很多东西都被简单地跳过了。这可能是你前进的最佳方式。 - mghie

2

我一段时间前发现了这个问题,并通过在“临时”框架中托管控件来显著提高创建时间(即不分配给表单的框架)。我认为缓慢是由于每个控件与父表单进行大量调用(例如SetBounds,SetVisible等)而最终导致的。通过使用浮动框架,您可以快速完成此操作,然后将框架分配给所需的表单。


1

你可以将数据的实际检索卸载到后台线程中,但 UI 相关的操作必须在同一个线程(即主线程)中进行。因此,你的框架的实际设置也会在同一个线程中进行。

你尝试过使用分析器吗?可能是你的 GUI 连接过于复杂,更新/连接导致了许多不必要的事件/副作用。使用分析器,你可以更深入地了解到实际上是什么导致了低性能。例如,它可能表明你花费了大量时间等待数据库返回,或者每个设置都触发了一个事件,进而触发另一个事件。


1
如果您使用数据控件,请确保在TDataSet上调用DisableControls。这也可能导致许多重绘。

0
只是一个猜测:也许将控件的父级以相反的层次顺序设置会有所帮助,即首先设置最深嵌套控件的父级,然后是它们的父级,依此类推。这可能会减少一些不必要的刷新,因为Windows还不知道您的控件。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接