Create and then destroy TLabels at runtime in Firemonkey

1.5k views Asked by At

I'm trying to generate TLabels at runtime and insert them into a VertScrollBox with this code;

var
   i, f: integer;
   RT_Label: TLabel;
begin
   f:= 10;
   for i := 0 to 20 do
   begin
        RT_Label := TLabel.Create(Self);
        RT_Label.Name := 'Label' + i.ToString;
        RT_Label.Text := 'SampleLabel' + i.ToString;
        RT_Label.Position.Y := f;
        RT_Label.Align := TAlignLayout.Top;
        RT_Label.Parent := VertScrollBox1;
        inc(f, 15);
   end;
end; 

Labels are displayed without any problem, but when I try to free the generated labels with this code:

var
   i: integer;
   LComponent: TComponent;
begin
   for i := 0 to ComponentCount-1 do
   begin
        if( Components[i] is TLabel )then
         if StartsText('Label', (Components[i] as TLabel).Name) then
         begin
             LComponent := (Components[i] as TLabel);     
             If Assigned(LComponent) then FreeAndNil(LComponent);
         end;
    end;
end;

Then I always get the error 'Argument out of range'.

How do I properly remove TLabels added to the VertScrollBox in runtime?

1

There are 1 answers

0
Mike Sutton On BEST ANSWER

You start your loop with the following line

for i := 0 to ComponentCount-1 do

but when you free a component it removes itself from the Components list as part of it's clean-up code. So each component that gets freed reduces the size of the list by 1. The ComponentCount-1 expression is evaluated once when the for loop start and thus does not get updated to reflect the change.

Even if you could fix this your loop would be skipping items. I.e if your deleted item 3, item 4 would now become item 3, but your loop would advance to item 4.

The way around this is simple, though. Simply iterate the list backwards:

for i := ComponentCount-1 downto 0 do

It's worth mentioning that your code will only actually free items on Windows and OSX. On mobile the compiler uses ARC which only frees an object once all references it have been removed. The solution/work around/fudge[1] is to call DisposeOf instead of Free for components.

As an aside, the as operator already guarantees that the object is Assigned, so no need for the extra test. There's no need to FreeAndNil a local variable which will be either reassigned to or go straight out of scope, and there's no need to cast an object before freeing it. Since the Free (or DisposeOf) method is present in the common ancestor class the compiler will resolve links for any descendant classes.

Thus, your code can be simplified to:

var
  i: integer;
begin
  for i := ComponentCount-1 downto 0 do
  begin
    if Components[i] is TLabel then
      if StartsText('Label', (Components[i] as TLabel).Name) then
        Components[i].DisposeOf;
  end;
end;

[1] - depending on who you talk to.