6

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?

Kromster
  • 7,181
  • 7
  • 63
  • 111
Alvin Lin
  • 199
  • 2
  • 5
  • 13
  • Seeing the code you've posted in this and some other qs,you seem to be doing things like dynamically creating components which are normally completely unnecessary, and running into problems as a result. If you would explain *why* you're creating these components, maybe readers could suggest other, less error-prone, ways of doing it. E.g. if this is to do with records read from a database, have you looked at the standard TDBCtrlGrid, which is basically a scrollbox that you can populate with DB-aware controls which get replicated for each row in its source dataset? – MartynA Aug 07 '14 at 07:17
  • Thank you for your reply. Yes I did look into details on TDBCtrlGrid and learned how to use it. However, then I found out that it does not support mobile applications which is what I am working on. – Alvin Lin Aug 07 '14 at 08:24
  • Ah, ok. Normally, I would delete my comment as not very relevant, but in this case I will leave it in case other readers have similar thoughts as I did. Are your controls to allow the user to change the data or just for display? – MartynA Aug 07 '14 at 08:29
  • My controls are to allow users to add/delete the data which gives me a lot of troubles... Sorry for all the questions that I have asked as I am new to delphi. This is just a little assignment I gave myself to learn how to write a mobile application that interacts with database. Any response is much appreciated. – Alvin Lin Aug 07 '14 at 08:38

2 Answers2

18

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.

Guillem Vicens
  • 3,936
  • 30
  • 44
  • Thanks for your reply. I just tried that but my program still crashes...any idea why?? – Alvin Lin Aug 07 '14 at 06:31
  • When I run debugger, it shows "Project xxx.exe raised exception class $C0000005 with message 'access violation at 0x0080b8b7: read of address 0x00000014'. Process xxx.exe (3484) – Alvin Lin Aug 07 '14 at 06:41
  • 1
    @AlvinLIn - That's a typical message for trying to access a method on a nil object. I would add a debugmessage telling you what control is getting freed and work from there. Probably the control being freed is doing a call on an unassigned childobject. – Lieven Keersmaekers Aug 07 '14 at 06:50
  • @Guillem There's nested parentage here. Recursion, or equivalent, is needed. – David Heffernan Aug 07 '14 at 17:58
  • @Alvin There's no point asking us to debug fragments. Show a complete program. Why make it hard for us? Do you want help or not? – David Heffernan Aug 07 '14 at 17:59
  • 1
    @DavidHeffernan I am sorry that I didn't try to make it hard for you . I just thought it might be better that I pinpoint out the exact question that I had, so I wrote out this little fragments. The actual program may take some time to go through...but here it is https://www.dropbox.com/s/2kmlaor8jqrzb6f/Stock_2.rar Thank you for your help. – Alvin Lin Aug 08 '14 at 02:55
  • Never mind. If I can't see the program, I cannot help. – David Heffernan Aug 08 '14 at 04:22
  • @DavidHeffernan, thanks for the hint. I missed it when I answered first. I will update my answer to reflect it. – Guillem Vicens Aug 08 '14 at 08:25
  • @GuillemVicens Also be aware that this is an FMX project – David Heffernan Aug 08 '14 at 08:29
3

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.

Jan Doggen
  • 8,799
  • 13
  • 70
  • 144
Abelisto
  • 14,826
  • 2
  • 33
  • 41
  • There is some problems with understanding what is `parent` and what is `owner`. `parent` is the 'surface' where control shown. `owner` is the 'owner' :-) – Abelisto Aug 07 '14 at 12:17
  • 3
    `Label1` will be destroyed regardless (I also made that mistake and deleted my answer). `TControl.SetParent` causes `AParent.InsertControl(Self);` and the Parent *will* destroy it's child controls. – kobik Aug 07 '14 at 12:24