segunda-feira, maio 26, 2008

Recursive component's enumerator in Delphi

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.

2 comentários:

Daniel Maltarolli disse...

Very nice!

Martina disse...

How so great this recursive component's enumberator in Dlphi.