Delphi - 如何在运行时删除所有子组件?

6

在设计时,我创建了一个TScrollBox,它将成为运行时创建的TLayout的父控件。 这些布局也会包含像这样的TLabel和TEdit:

var
  Layout1: TLayout;
  Label1: TLabel;
  Edit1: TEdit;
begin
  Layout1 := TLayout.Create(self);
  Layout1.Parent := ScrollBox1;
  Label1 := TLabel.Create(self);
  Label1.Parent := Layout1;
  Label1.Text := 'abc';
end;

现在我想要像这个过程从未被调用过一样删除所有内容。
我已经尝试了以下方法,但程序会崩溃。
var
  i : integer;
  Item : TControl;
begin
  for i := 0 to Scrollbox1.ControlCount - 1 do  
  begin  
    Item := Scrollbox1.controls[i];
    Item.Free;
  end;
end;

请问有谁能给我一些提示吗?


看到您在这个和其他一些问题中发布的代码,似乎您正在做一些通常完全不必要的动态创建组件的事情,并因此遇到了问题。如果您能解释一下为什么要创建这些组件,也许读者可以建议其他更少出错的方法。例如,如果这与从数据库中读取的记录有关,您是否查看过标准的TDBCtrlGrid,它基本上是一个滚动框,您可以用DB-aware控件填充它,这些控件会为其源数据集中的每一行复制? - MartynA
谢谢您的回复。是的,我确实仔细研究了TDBCtrlGrid并学会了如何使用它。然而,后来我发现它不支持移动应用程序,而我正在处理的就是移动应用程序。 - Alvin Lin
啊,好的。通常情况下,我会删除我的评论,因为它不是很相关,但在这种情况下,我会将其保留,以防其他读者有与我类似的想法。您的控件是用于允许用户更改数据还是仅用于显示? - MartynA
我的控件允许用户添加/删除数据,这给我带来了很多麻烦...抱歉我问了这么多问题,因为我是delphi的新手。这只是我给自己的一个小任务,学习如何编写与数据库交互的移动应用程序。非常感谢任何回复。 - Alvin Lin
2个回答

18

当您删除一个控件时,它后面的控件在控件列表中的索引会向下移动。也就是说,您最终尝试访问不存在的位置。

您需要向下迭代列表:

var
  i : integer;
  Item : TControl;
begin
  for i := (Scrollbox1.ControlCount - 1) downto 0 do  
  begin  
    Item := Scrollbox1.controls[i];
    Item.Free;
  end;
end;

另一种方法是始终停留在索引0,释放它的控制权并检查您是否仍有要释放的控件:

var
  i : integer;
  Item : TControl;
begin
  while Scrollbox1.ControlCount > 0 do  
  begin  
    Item := Scrollbox1.controls[0];
    Item.Free;
  end;
end;

更新

正如@DavidHeffernan所指出的那样,这里存在嵌套的父级关系。这意味着您应该从下至上释放组件。一种实现方式是使用递归

基本上,您需要一个过程来封装子控件的释放。代码类似于以下内容(请注意这只是我做的一个小测试,可能需要额外的代码):

procedure freeChildControls(myControl : TControl; freeThisControl: boolean);
var
  i : integer;
  Item : TControl;
begin

  if Assigned(myControl) then
  begin
    for i := (myControl.ControlsCount - 1) downto 0 do
    begin
      Item := myControl.controls[i];
      if assigned(item) then
        freeChildControls(item, childShouldBeRemoved(item));
    end;

    if freeThisControl then
      FreeAndNil(myControl);
  end;
end;

function childShouldBeRemoved(child: TControl): boolean;
begin
  //consider whatever conditions you need
  //in my test I just checked for the child's name to be layout1 or label1
  Result := ...; 
end;
为了使 scrollbox1 的子控件(但不包括它本身)自由,您需要像这样调用它:
freeChildControls(scrollbox1, false);

请注意,为了避免这个递归函数释放应该由其析构函数释放的layout的子控件,我不得不添加childShouldBeRemoved函数。

实现此功能的一种可能的解决方案是使用一个对象列表,在其中添加您创建的组件,然后在函数内部检查传递的子组件是否需要释放。


谢谢您的回复。我刚刚尝试了那个方法,但是我的程序仍然崩溃了...您有什么想法吗?? - Alvin Lin
当我运行调试器时,它显示“Project xxx.exe引发异常类$C0000005,消息为'access violation at 0x0080b8b7: read of address 0x00000014'。进程xxx.exe(3484)”。 - Alvin Lin
1
@AlvinLIn - 这是尝试访问空对象上的方法的典型消息。我建议添加一个调试信息,告诉您哪个控件被释放,并从那里开始工作。可能被释放的控件正在调用未分配的子对象。 - Lieven Keersmaekers
@Guillem 这里有嵌套的父级关系。需要使用递归或等效方法。 - David Heffernan
1
@DavidHeffernan 对不起,我并没有想要让你感到困难。我只是认为直接指出我的问题可能会更好,所以写了这些小片段。实际的程序可能需要一些时间才能完成......但是在这里它是https://www.dropbox.com/s/2kmlaor8jqrzb6f/Stock_2.rar 谢谢你的帮助。 - Alvin Lin
显示剩余4条评论

3
如果您在运行时创建组件,请将父控件作为构造函数的参数。例如:Label1 := TLabel.Create(Layout1);,这样父控件也是所有者。当您销毁Layout1时,Label1也会被销毁。

理解“parent”和“owner”的含义存在一些问题。“parent”是控件显示的“表面”,而“owner”则是“所有者” :-) - Abelisto
3
无论如何,Label1 都会被销毁(我也犯了这个错误并删除了我的答案)。TControl.SetParent 导致 AParent.InsertControl(Self);,而父控件 销毁其子控件。 - kobik

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