如何使用“ for / in”在另一个组件内部查找组件?

时间:2019-07-16 15:07:18

标签: delphi

我正在重构组件代码,发现以下代码:

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;

我今天不能很好地工作,但是我想在此方法中使用for / in,例如:

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

我已经尝试使用Generics.Collections像这样Self.Parent投射TObjectList<TMenuToolbarButton>(Self.Parent)

所以,我想知道是否有更好的方法使工作代码更“优雅”

2 个答案:

答案 0 :(得分:6)

ParentTWinControl,而不是TObjectList,因此您尝试的类型转换无效。

您不能直接将for.. in循环与Components属性一起使用,因为它不是一个满足任何documented requirements的可迭代容器:

  

Delphi在容器上支持for-element-in-collection样式的迭代。 编译器可以识别以下容器迭代模式

     
      
  • 对于ArrayExpr do Element中的元素;

  •   
  • 对于StringExpr do Stmt中的元素;

  •   
  • 对于SetExpr do Stmt中的元素;

  •   
  • 对于CollectionExpr do Stmt中的元素;

  •   
  • 对于“记录中的元素”,

  •   

Components属性不是数组,字符串,集合,集合或记录,因此不能通过for..in循环进行迭代。

但是,TComponent本身满足了可迭代集合的文档要求:

  

要在类或接口上使用for-in循环构造,该类或接口必须实现规定的收集模式。实现收集模式的类型必须具有以下属性:

     
      
  • 类或接口必须包含名为GetEnumerator()的公共实例方法。 GetEnumerator()方法必须返回类,接口或记录类型。

  •   
  • GetEnumerator()返回的类,接口或记录必须包含称为MoveNext()的公共实例方法。 MoveNext()方法必须返回一个Booleanfor-in循环首先调用此方法,以确保容器不为空。

  •   
  • GetEnumerator()返回的类,接口或记录必须包含一个公共实例,名为Current的只读属性。 Current属性的类型必须是集合中包含的类型。

  •   

TComponent有一个公用的GetEnumerator()方法,该方法返回一个TComponentEnumerator对象,该对象在内部迭代Components属性。但是,由于该属性处理TComponent对象,因此您仍然必须在循环内手动进行类型转换。

尝试一下:

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;

因此,在这种情况下,使用for..in循环并不能真正获得比传统for..to循环有用的任何东西。

答案 1 :(得分:4)

TComponent通过返回TComponentEnumerator的实例来实现方法GetEnumerator,该实例枚举该组件拥有的所有组件。为了使用此枚举器,您可以将局部变量声明更改为var MyMenuToolbarButton: TComponent;,但仍需要在循环内部进行类型转换。

如果您确实要使用for..in循环枚举指定类型的组件,则可以编写自己的通用枚举器:

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;

用法:

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;

一些注意事项:

  1. 这是一个非常幼稚的实现,无法针对某些极端情况提供保护,例如在迭代时更改组件集合,跨线程访问……当然,您的原始代码不执行任何操作,但是在编写通用类时,您应该考虑使其更加简单或记录其局限性。
  2. 此实现枚举类型为T的组件或其后代。
  3. 与简单的for..to循环相比,使用枚举器会添加small overhead