I needed to loop through all the components in some forms, and when you have a frame inside a form, the default TComponent's enumerator doesn't return the components which are inside the frame.
After goggling a little bit I found this nice reference: Fun with enumerators, which helped me understand how Delphi enumerator's work (and how complicated they are), so I managed to write a class helper to loop recursively through components.
Below is the unit full source code:
unit RecursiveEnumerator;
interface
uses Classes,
Contnrs;
type
TRecursiveComponentsEnum = class
private
FComponents: TComponentList;
FIndex: Integer;
function GetCurrent: TComponent;
public
constructor Create(AContainer: TComponent);
destructor Destroy; override;
function MoveNext: Boolean;
property Current: TComponent read GetCurrent;
end;
IRecursiveEnumeratorFactory = interface
function GetEnumerator: TRecursiveComponentsEnum;
end;
TRecursiveEnumeratorFactory = class(TInterfacedObject, IRecursiveEnumeratorFactory)
private
FContainer: TComponent;
public
function GetEnumerator: TRecursiveComponentsEnum;
constructor Create(AContainer: TComponent);
end;
TRecursiveEnumeratorComponentHelper = class helper for TComponent
public
function GetRecursiveEnumerator: IRecursiveEnumeratorFactory;
end;
implementation
uses
SysUtils;
constructor TRecursiveComponentsEnum.Create(AContainer: TComponent);
procedure AddToList(AComponent: TComponent);
var
lComponent: TComponent;
begin
for lComponent in AComponent do
begin
if lComponent.ComponentCount > 0 then
AddToList(lComponent);
FComponents.Add(lComponent);
end;
end;
begin
FComponents := TComponentList.Create(False);
AddToList(AContainer);
FIndex := -1;
end;
destructor TRecursiveComponentsEnum.Destroy;
begin
FComponents.Free;
inherited;
end;
function TRecursiveComponentsEnum.GetCurrent: TComponent;
begin
Result := FComponents.Items[FIndex];
end;
function TRecursiveComponentsEnum.MoveNext: Boolean;
begin
Result := FIndex < FComponents.Count - 1;
if Result then
Inc(FIndex);
end;
function TRecursiveEnumeratorComponentHelper.GetRecursiveEnumerator: IRecursiveEnumeratorFactory;
begin
Result := TRecursiveEnumeratorFactory.Create(Self);
end;
constructor TRecursiveEnumeratorFactory.Create(AContainer: TComponent);
begin
FContainer := AContainer;
end;
function TRecursiveEnumeratorFactory.GetEnumerator: TRecursiveComponentsEnum;
begin
Result := TRecursiveComponentsEnum.Create(FContainer);
end;
end.
It is a class helper, to use it, just add the unit to the uses list and use it like this:
var
Component: TComponent;
begin
for Component in GetRecursiveEnumerator do
// do something with the component
end;
The magic here is that it doesn't loop trough the components "on the fly". When you call the GetRecursiveEnumerator a TRecursiveComponentsEnum instance is created, at this time, it loops recursively trough all the component's underlying components and fill a TComponentList (FComponents), then the GetCurrent method return's the components from this list, and it just works.
constructor TRecursiveComponentsEnum.Create(AContainer: TComponent);
procedure AddToList(AComponent: TComponent);
var
lComponent: TComponent;
begin
for lComponent in AComponent do
begin
if lComponent.ComponentCount > 0 then
AddToList(lComponent);
FComponents.Add(lComponent);
end;
end;
begin
FComponents := TComponentList.Create(False);
AddToList(AContainer);
FIndex := -1;
end;
Hope I'm not reinventing the wheel. If you know a better solution, please let me know.