views:

903

answers:

5

This is probably a stupid question, but my brain is just cooked enough I think I'm going to use one of my "lifelines" to see if I can get some help from my stack overflow friends. ;)

I need to delete all occurrences of a particular component type on my main form (some of them are inside panels or tabsheets, but all on and owned by the same form). Here's what I have now:

for i := 0 to frmMain.ComponentCount - 1 do  
  begin  
    if frmMain.Components[i] is TMyClass then frmMain.Components[i].Destroy;  
  end;

The problem is (and I knew it would be before I compiled it) that once I destroy the component, the form's component list re-indexes and I end up out of bounds.

What's the best way to solve this? I thought about adding the "found" components to a standalone array, and then walk through that after this loop to delete them, which I think will work.... but is that the best approach?

TIA


UPDATE:

You guys rock. Thanks. : )

+7  A: 

Start at the top and work backwards.

viz:

for i := frmMain.ComponentCount - 1 downto 0 do
begin
  if frmMain.Components[i] is TMyClass then frmMain.Components[i].Free;
end;

Call free instead of Destroy. Free calls Destroy after checking for a valid reference.

He should also call Free and not Destroy. One should never call Destroy, and always call Free.
Nick Hodges
+11  A: 

You're nearly right. Your loop should look like

for i := frmMain.ComponentCount - 1 downto 0 do
begin
  if frmMain.Components[i] is TMyClass then
    frmMain.Components[i].Free;
end;

This way the call to the function "frmMain.ComponentCount" gets done at the beginning and not again.

You should also call Free as above, not Destroy - I can't remember why at the moment. Bri

Brian Frost
Destroy is virtual. If the object has been destroyed already then it would fail. Free checks to see if it has a valid reference before calling Destroy. Not likely to be a problem here, but a good practice in general.
Jim McKeeth
In this case, it is safe to just call Destroy. Because of the way VCL manages this list, there is not likely to be an invalid reference in that list. Even if there were, Free wouldn't protect you because it relies on the instance being nil.
Allen Bauer
Note also that the loop goes from high to zero, to ensure all items are considered, otherwise the loop can skip items next to the deleted one. Important not to miss that.
mj2008
+1  A: 

The same solution with a while-loop:

i := 0;
while i < frmMain.ComponentCount do
begin
  if frmMain.Components[i] is TMyClass then
    frmMain.Components[i].Free 
  else
    inc(i);
end;
Vegar
this will not work - (i) is going up and (frmMain.ComponentCount) is comming down
Charles Faiga
If you take a second look, you will see that i want come up if componentcount goes down. If every single component is of class TMyClass, i would remain at zero, and the while-loop would terminate, because 0 < 0 is false
Vegar
+2  A: 

It may not happen in your case, but the "if frmMain.Components[i] is TMyClass" check will also return true for descendant classes of TMyClass. If you are really looking for the removal of one specific class, you may need to add an extra check of the ClassName.

pritaeas
A: 

If you need to check & destroy a named known component Use

If YourComponent <> Nil Then
  YourComponent.Free;
ArafatZG
This is more ore less the same as `if YourComponent <> nil then if YourComponent <> nil then YourComponent.Destroy;`The double if is not **that** sensible, is it? :-)
Ulrich Gerhardt