Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use "for/in" to find components inside of another component?

Tags:

delphi

I'm refactoring a component code, and I found the follow code:

procedure TMenuToolbarButton.ClearActivation;
var
  i: Integer;
begin
  for i := 0 to Self.Parent.ComponentCount -1 do
  begin
    if (Self.Parent.Components[i] is TMenuToolbarButton) then
    begin
      (Self.Parent.Components[i] as TMenuToolbarButton).FActivatedImage.Visible := False;
      (Self.Parent.Components[i] as TMenuToolbarButton).FActivatedImageGrowLeft.Visible := False;
    end;
  end;
end;

I'ts working perfectly today, but i want to use for/in in this method, something like this:

procedure TMenuToolbarButton.ClearActivation;
var
  MyMenuToolbarButton: TMenuToolbarButton;
begin
  for MyMenuToolbarButton in Self.Parent do
  begin
    MyMenuToolbarButton.FActivatedImage.Visible         := False;
    MyMenuToolbarButton.FActivatedImageGrowLeft.Visible := False;
  end;
end;

I already tried with Generics.Collections casting the Self.Parent like this: TObjectList<TMenuToolbarButton>(Self.Parent)

So, I want to know if is there a better way to make the working code more "elegant"

like image 215
Victor Zanella Avatar asked Nov 29 '25 18:11

Victor Zanella


2 Answers

The Parent is a TWinControl, not a TObjectList, so your attempted typecast is invalid.

You can't use a for.. in loop with the Components property directly, as it is not an iterable container that meets any of the documented requirements:

Delphi supports for-element-in-collection style iteration over containers. The following container iteration patterns are recognized by the compiler:

  • for Element in ArrayExpr do Stmt;

  • for Element in StringExpr do Stmt;

  • for Element in SetExpr do Stmt;

  • for Element in CollectionExpr do Stmt;

  • for Element in Record do Stmt;

The Components property is not an Array, a String, a Set, a Collection, or a Record, so it can't be iterated by a for..in loop.

However, TComponent itself satisfies the documented requirements of an iterable Collection:

To use the for-in loop construct on a class or interface, the class or interface must implement a prescribed collection pattern. A type that implements the collection pattern must have the following attributes:

  • The class or interface must contain a public instance method called GetEnumerator(). The GetEnumerator() method must return a class, interface, or record type.

  • The class, interface, or record returned by GetEnumerator() must contain a public instance method called MoveNext(). The MoveNext() method must return a Boolean. The for-in loop calls this method first to ensure that the container is not empty.

  • The class, interface, or record returned by GetEnumerator() must contain a public instance, read-only property called Current. The type of the Current property must be the type contained in the collection.

TComponent has a public GetEnumerator() method which returns a TComponentEnumerator object that internally iterates the Components property. But, since the property deals with TComponent objects, you will still have to manually typecast them inside the loop.

Try this:

procedure TMenuToolbarButton.ClearActivation;
var
  //i: Integer;
  Comp: TComponent;
  Btn: TMenuToolbarButton;
begin
  //for i := 0 to Self.Parent.ComponentCount -1 do
  for Comp in Self.Parent do
  begin
    //Comp := Self.Parent.Components[i];
    if Comp is TMenuToolbarButton then
    begin
      Btn := TMenuToolbarButton(Comp);
      Btn.FActivatedImage.Visible := False;
      Btn.FActivatedImageGrowLeft.Visible := False;
    end;
  end;
end;

So, using a for..in loop does not really gain you anything useful over a traditional for..to loop in this situation.

like image 191
Remy Lebeau Avatar answered Dec 01 '25 10:12

Remy Lebeau


TComponent implements method GetEnumerator by returning instance of TComponentEnumerator, which enumerates all components owned by this component. In order to use this enumerator you could change your local variable declaration to var MyMenuToolbarButton: TComponent;, but you would still need to type-cast inside the loop.

If you really, really want to use for..in loop for enumerating components of specified type, you can write your own generic enumerator:

type
  TComponentEnumerator<T: TComponent> = record
  private
    FIndex: Integer;
    FComponent: TComponent;
  public
    constructor Create(AComponent: TComponent);
    function GetCurrent: T; inline;
    function GetEnumerator: TComponentEnumerator<T>;
    function MoveNext: Boolean;
    property Current: T read GetCurrent;
  end;

constructor TComponentEnumerator<T>.Create(AComponent: TComponent);
begin
  FIndex := -1;
  FComponent := AComponent;
end;

function TComponentEnumerator<T>.GetCurrent: T;
begin
  Result := T(FComponent.Components[FIndex]);
end;

function TComponentEnumerator<T>.GetEnumerator: TComponentEnumerator<T>;
begin
  Result := Self;
end;

function TComponentEnumerator<T>.MoveNext: Boolean;
begin
  Inc(FIndex);
  while (FIndex < FComponent.ComponentCount) and (not (FComponent.Components[FIndex] is T)) do
    Inc(FIndex);
  Result := FIndex < FComponent.ComponentCount;
end;

Usage:

procedure TMenuToolbarButton.ClearActivation;
var
  MyMenuToolbarButton: TMenuToolbarButton;
begin
  for MyMenuToolbarButton in TComponentEnumerator<TMenuToolbarButton>.Create(Self.Parent) do
  begin
    MyMenuToolbarButton.FActivatedImage.Visible         := False;
    MyMenuToolbarButton.FActivatedImageGrowLeft.Visible := False;
  end;
end;

Few notes:

  1. This is pretty naïve implementation which is not protected against some edge cases like changing the components collection while iterating, cross-thread access, ... Of course your original code does none of that, but when you write general purpose class, you should consider making it more foolproof or document its limitations.
  2. This implementation enumerates components of type T or its descendants.
  3. Using enumerators adds small overhead when compared to simple for..to loop.
like image 32
Peter Wolf Avatar answered Dec 01 '25 10:12

Peter Wolf



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!