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

Posted

技术标签:

【中文标题】Delphi - 如何在运行时删除所有子组件?【英文标题】:Delphi - How to delete all child components at runtime? 【发布时间】:2014-09-30 06:08:46 【问题描述】:

在设计时,我创建了一个 TScrollBox,它将作为在运行时创建的 TLayouts 的父级。 布局还将包含 Tlabels 和 Tedits,如下所示:

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 感知控件填充它,这些控件会为其源数据集中的每一行复制? 感谢您的回复。是的,我确实查看了 TDBCtrlGrid 的详细信息并学习了如何使用它。但是,后来我发现它不支持我正在开发的移动应用程序。 啊,好的。通常,我会删除我的评论,因为不是很相关,但在这种情况下,我会留下它,以防其他读者和我有类似的想法。您的控件是允许用户更改数据还是仅用于显示? 我的控件是允许用户添加/删除数据,这给我带来了很多麻烦......对不起,我问的所有问题,因为我是 delphi 新手。这只是我给自己的一个小任务,用于学习如何编写与数据库交互的移动应用程序。非常感谢任何回应。 【参考方案1】:

当您删除一个控件时,control list 中它后面的索引会向下移动。即,您最终会尝试访问不存在的职位。

你需要向下迭代列表:

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);

请注意,我必须添加 childShouldBeRemoved 函数以避免此递归函数释放 labellayout 的子控件,您应该将它们留给它们的析构函数释放。

实现此功能的一种可能解决方案是使用object list,您将在其中添加创建的组件,然后在函数内部检查是否必须释放传递的子组件。

【讨论】:

感谢您的回复。我刚试过,但我的程序仍然崩溃......知道为什么吗?? 当我运行调试器时,它显示“项目 xxx.exe 引发异常类 $C0000005 并带有消息'访问冲突在 0x0080b8b7: 读取地址 0x00000014'。进程 xxx.exe (3484) @AlvinLIn - 这是尝试访问 nil 对象上的方法的典型消息。我会添加一条调试消息,告诉您哪些控件正在被释放并从那里开始工作。可能正在释放的控件正在调用未分配的子对象。 @Guillem 这里有嵌套的亲子关系。需要递归或等效的。 @DavidHeffernan 很抱歉,我没有努力让你难过。我只是认为最好指出我遇到的确切问题,所以我写了这些小片段。实际程序可能需要一些时间才能完成...但这里是dropbox.com/s/2kmlaor8jqrzb6f/Stock_2.rar 谢谢您的帮助。【参考方案2】:

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

【讨论】:

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

以上是关于Delphi - 如何在运行时删除所有子组件?的主要内容,如果未能解决你的问题,请参考以下文章

Delphi FMX组件重影去除子组件

delphi中,怎样设置新窗体打开时,就运行指定的SQL语句

如何设计delphi 窗体里面的组件居中

Delphi XE2 运行时不考虑组件属性

在单独的设计时包中覆盖组件创建构造函数

删除元素后,子组件无法从其父组件获取数组。