Wednesday, February 06, 2008

TWinControl.Controls Enumerator, Revisited

Fredrik Loftheim made an interesting observation to my previous article on TWinControl.Controls enumerator - namely that the enumerator factory doesn't have to be interface based. It can be replaced by a record. Source is almost the same as before, but we can skip the interface declaration. Generated code is simpler as the enumerator factory is simply allocated from the stack and automatically destroyed when it goes out of scope. No interface support and no reference counting is required. Simpler and faster, that's the way to go.

interface

type
TControlEnumerator = record
strict private
ceClass : TClass;
ceIndex : integer;
ceParent: TWinControl;
public
constructor Create(parent: TWinControl; matchClass: TClass);
function GetCurrent: TControl;
function MoveNext: boolean;
property Current: TControl read GetCurrent;
end; { TControlEnumerator }

TControlEnumeratorFactory = record
strict private
cefClass : TClass;
cefParent: TWinControl;
public
constructor Create(parent: TWinControl; matchClass: TClass);
function GetEnumerator: TControlEnumerator;
end; { TControlEnumeratorFactory }

function EnumControls(parent: TWinControl; matchClass: TClass = nil): TControlEnumeratorFactory;

implementation

function EnumControls(parent: TWinControl; matchClass: TClass = nil): TControlEnumeratorFactory;
begin
Result := TControlEnumeratorFactory.Create(parent, matchClass);
end; { EnumControls }

{ TControlEnumerator }

constructor TControlEnumerator.Create(parent: TWinControl; matchClass: TClass);
begin
ceParent := parent;
ceClass := matchClass;
ceIndex := -1;
end; { TControlEnumerator.Create }

function TControlEnumerator.GetCurrent: TControl;
begin
Result := ceParent.Controls[ceIndex];
end; { TControlEnumerator.GetCurrent }

function TControlEnumerator.MoveNext: boolean;
begin
Result := false;
while ceIndex < (ceParent.ControlCount - 1) do begin
Inc(ceIndex);
if (ceClass = nil) or (ceParent.Controls[ceIndex].InheritsFrom(ceClass)) then begin
Result := true;
break; //while
end;
end; //while
end; { TControlEnumerator.MoveNext }

{ TControlEnumeratorFactory }

constructor TControlEnumeratorFactory.Create(parent: TWinControl; matchClass: TClass);
begin
cefParent := parent;
cefClass := matchClass;
end; { TControlEnumeratorFactory.Create }

function TControlEnumeratorFactory.GetEnumerator: TControlEnumerator;
begin
Result := TControlEnumerator.Create(cefParent, cefClass);
end; { TControlEnumeratorFactory.GetEnumerator }

The new version of the TWinControl.Controls enumerator plus some other stuff is available at http://gp.17slon.com/gp/files/gpvcl.zip.

6 comments:

  1. Also why not add the function to the TWinControl class using a class helper, would make the code using it look nicer.

    ReplyDelete
  2. I'm trying not to use class helpers too much, mainly because of the weird implementation which only allows one active helper per class.

    And in this case, there's not much difference between typing EnumControls(control) and control.EnumControls.

    ReplyDelete
  3. You can use the syntax

    TYPE
    THelperClass2 = CLASS HELPER(THelperClass1) FOR TClassToBeHelped

    to have more than one active CLASS HELPER for a given class...

    ReplyDelete
  4. I'm aware of that.

    The problem occurs when you have two class helpers for TWinControl in two different units, written by different authors. Then you have to manually change one of them to descend from the other class helper - and you have to do it every time the unit changes.

    In my opinion a simple function is better and more stable.

    ReplyDelete
  5. Is there a reason the link is broken to http://gp.17slon.com/gp/files/gpvcl.zip? We still can't enumerate over TWinControl.Controls, can we?

    ReplyDelete
    Replies
    1. Link rot, sorry.

      GpVCL was moved to GitHub: https://github.com/gabr42/GpDelphiUnits/blob/master/src/GpVCL.pas

      Delete