Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi - How to delete all child components at runtime?

At design time, I create a TScrollBox which will be the parent of TLayouts created at runtime. The Layouts will also contain Tlabels and Tedits like this:

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;

Now I want to delete everything out like this procedure has never been called.

I have tried the following, but the program would just crash.

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

Can anyone please give me a hint?

like image 594
Alvin Lin Avatar asked Aug 07 '14 05:08

Alvin Lin


2 Answers

When you remove a control, the index of the ones behind it in the control list shifts down. I.e, you end up trying to access positions that do not exist.

You need to iterate the list downwards:

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

Another way is to stay always at index 0, free its control and check that you still have controls to free:

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

UPDATE

As @DavidHeffernan pointed out, there is nested parentage here. This means you should free your components from bottom up. One way to do it is by recursion.

Basically you would need a procedure to encapsulate the freeing of child controls. The code would be similar to following (please note this is just a small test I did and extra code may be required):

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;

In order to free the scrollbox1 child controls (but not itself) you would call it like this:

freeChildControls(scrollbox1, false);

Please note that I had to add the childShouldBeRemoved function in order to avoid this recursive function to free child controls of the label and layout that you should leave for their destructors to free.

One possible solution to implement this function would be to use an object list where you would add your created components, and then inside the function check if the passed child component has to be freed.

like image 82
Guillem Vicens Avatar answered Oct 19 '22 05:10

Guillem Vicens


If you create components at runtime - use parent control as parameter of the constructor. Like Label1 := TLabel.Create(Layout1); - so that the parent is also the owner. When you destroy Layout1 the Label1 also will be destroyed.

like image 23
Abelisto Avatar answered Oct 19 '22 04:10

Abelisto