Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   .Controls und .Components nicht als Auflistung nutzbar? (https://www.delphipraxis.net/210560-controls-und-components-nicht-als-auflistung-nutzbar.html)

freejay 11. Mai 2022 12:46

.Controls und .Components nicht als Auflistung nutzbar?
 
Hallo zusammen,

kann es sein, dass man das Folgende immer noch nicht machen kann:

Delphi-Quellcode:
for MyControl in Panel1.Controls do


Muss man immer noch schreiben

Delphi-Quellcode:
for i := 0 to Panel1.ControlCount - 1 do


oder mache ich da bloß was falsch?

Danke im voraus!

Freejay

Uwe Raabe 11. Mai 2022 13:54

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
Das ist immer noch so und einfach der Tatsache geschuldet, dass Controls ein Array-Property und eben kein Array ist.

Es gibt aber Abhilfe - und die ist sogar noch besser. Mit dem Code weiter unten kann man nämlich Folgendes schreiben:
Delphi-Quellcode:
  for var ctrl in ControlsOf<TButton> do
    ctrl.Caption := 'Hello World';
Delphi-Quellcode:
type
  TWinControlHelper = class helper for TWinControl
  type
    TControlEnumerator<T: TControl> = class
    private
      FIndex: Integer;
      FWinControl: TWinControl;
      function GetCurrent: T;
    public
      constructor Create(AWinControl: TWinControl);
      function MoveNext: Boolean;
      property Current: T read GetCurrent;
    end;

    IControls<T: TControl> = interface
      function GetEnumerator: TControlEnumerator<T>;
    end;

    TControls<T: TControl> = class(TInterfacedObject, IControls<T>)
    private
      FWinControl: TWinControl;
    public
      constructor Create(AWinControl: TWinControl);
      function GetEnumerator: TControlEnumerator<T>;
    end;
  public
    function ControlsOf<T: TControl>: IControls<T>;
  end;

{ TWinControlHelper }

function TWinControlHelper.ControlsOf<T>: IControls<T>;
begin
  Result := TControls<T>.Create(Self);
end;

{ TWinControlHelper.TControls<T> }

constructor TWinControlHelper.TControls<T>.Create(AWinControl: TWinControl);
begin
  inherited Create;
  FWinControl := AWinControl;
end;

function TWinControlHelper.TControls<T>.GetEnumerator: TControlEnumerator<T>;
begin
  Result := TControlEnumerator<T>.Create(FWinControl);
end;

constructor TWinControlHelper.TControlEnumerator<T>.Create(AWinControl: TWinControl);
begin
  inherited Create;
  FWinControl := AWinControl;
  FIndex := -1;
end;

function TWinControlHelper.TControlEnumerator<T>.GetCurrent: T;
begin
  Result := FWinControl.Controls[FIndex] as T;
end;

function TWinControlHelper.TControlEnumerator<T>.MoveNext: Boolean;
begin
  repeat
    Inc(FIndex);
    if FIndex >= FWinControl.ControlCount then
      Exit(False);
    if FWinControl.Controls[FIndex] is T then
      Exit(True);
  until False;
end;

Uwe Raabe 11. Mai 2022 14:16

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
Hier auch noch das Pendant für Components:
Delphi-Quellcode:
type
  TComponentHelper = class helper for TComponent
  type
    TComponentEnumerator<T: TComponent> = class
    private
      FIndex: Integer;
      FComponent: TComponent;
      function GetCurrent: T;
    public
      constructor Create(AComponent: TComponent);
      function MoveNext: Boolean;
      property Current: T read GetCurrent;
    end;

    IComponents<T: TComponent> = interface
      function GetEnumerator: TComponentEnumerator<T>;
    end;

    TComponents<T: TComponent> = class(TInterfacedObject, IComponents<T>)
    private
      FComponent: TComponent;
    public
      constructor Create(AComponent: TComponent);
      function GetEnumerator: TComponentEnumerator<T>;
    end;
  public
    function ComponentsOf<T: TComponent>: IComponents<T>;
  end;

{ TComponentHelper }

function TComponentHelper.ComponentsOf<T>: IComponents<T>;
begin
  Result := TComponents<T>.Create(Self);
end;

{ TComponentHelper.TComponents<T> }

constructor TComponentHelper.TComponents<T>.Create(AComponent: TComponent);
begin
  inherited Create;
  FComponent := AComponent;
end;

function TComponentHelper.TComponents<T>.GetEnumerator: TComponentEnumerator<T>;
begin
  Result := TComponentEnumerator<T>.Create(FComponent);
end;

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

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

function TComponentHelper.TComponentEnumerator<T>.MoveNext: Boolean;
begin
  repeat
    Inc(FIndex);
    if FIndex >= FComponent.ComponentCount then
      Exit(False);
    if FComponent.Components[FIndex] is T then
      Exit(True);
  until False;
end;

Uwe Raabe 11. Mai 2022 14:23

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
Interessanterweise gibt es bereits einen Enumerator für Components, allerdings nicht generisch:
Delphi-Quellcode:
  for var cmp in Self do
    if cmp is TButton then
      TButton(cmp).Caption := 'Hello World';

freejay 11. Mai 2022 15:45

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
Vielen Dank!

Nicht falsch verstehen jetzt (ich bin sehr dankbar für die aufgezeigte Lösung), aber:

Das ist sicher besser als for i := 0 to ... aber es sieht wie eine Funktion aus, die man mit dem Control aufruft und das wirkt irgendwie "un-objektorientiert"...

Ich nehme an diese Helper sind (noch) nicht Bestandteil von Delphi?

Uwe Raabe 11. Mai 2022 16:04

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
Zitat:

Zitat von freejay (Beitrag 1505711)
aber es sieht wie eine Funktion aus, die man mit dem Control aufruft und das wirkt irgendwie "un-objektorientiert"

Kannst du diese Aussage etwas präzisieren?
Was am Aufruf einer function eines Objekts ist denn un-objektorientiert? (Oder allgemeiner: Was bedeutet un-objektorientiert überhaupt?)
Würde diese function ein
Delphi-Quellcode:
TArray<TControl>
zurückgeben, was ja problemlos mit for-in iteriert werden kann, wäre das dann auch un-objektorientiert?
Immerhin wird sowas ja bereits an vielen Stellen in Delphi gemacht, z.B.
Delphi-Quellcode:
TStringList.ToStringArray
.

Zitat:

Zitat von freejay (Beitrag 1505711)
Ich nehme an diese Helper sind (noch) nicht Bestandteil von Delphi?

Nein, die sind von mir. Wären sie in Delphi drin, wären es vermutlich keine Helper, sondern die Funktionalität wäre direkt in TComponent bzw. TWinControl implementiert.

mkinzler 11. Mai 2022 16:06

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
Zitat:

Das ist sicher besser als for i := 0 to ... aber es sieht wie eine Funktion aus, die man mit dem Control aufruft und das wirkt irgendwie "un-objektorientiert"...
Delphi-Quellcode:
for var ctrl in ControlsOf<TButton> do
    ctrl.Caption := 'Hello World';
Doch ist doch objektorientiert. Oder wie sollte es nach Deiner Meinung aussehen, wenn es objektorientiert wäre?

Uwe Raabe 11. Mai 2022 16:09

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
Man sollte vielleicht erwähnen, dass das ControlsOf in diesem Fall ja das des Forms ist, in dessen Event-Handler es aufgerufen wird. Wollte man das auf ein Panel anwenden, um z.B. die Buttons auf diesem Panel zu verarbeiten, müsste man das so schreiben:
Delphi-Quellcode:
for var ctrl in Panel1.ControlsOf<TButton> do
  ctrl.Caption := 'Hello World';

Medium 11. Mai 2022 21:47

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1505712)
die Funktionalität wäre direkt in TComponent bzw. TWinControl implementiert.

Stellt sich die Frage: Wieso ist es das eigentlich nicht? Das ist doch ziemlich ein Paradebeispiel für solch ein Konstrukt. Bin ich auch schon kräftig drüber gestolpert, und war doch recht verwundert ob der Tatsachen.

himitsu 11. Mai 2022 21:57

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
für Components gibt es bereits einen Enumerator

Delphi-Quellcode:
for var C in IrgendeineTComponentInstanz do

Uwe Raabe 11. Mai 2022 23:04

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
Zitat:

Zitat von Medium (Beitrag 1505718)
Stellt sich die Frage: Wieso ist es das eigentlich nicht?

Ist es doch - zumindest teilweise:
Zitat:

Zitat von Uwe Raabe (Beitrag 1505707)
Interessanterweise gibt es bereits einen Enumerator für Components, allerdings nicht generisch:
Delphi-Quellcode:
  for var cmp in Self do
    if cmp is TButton then
      TButton(cmp).Caption := 'Hello World';


freejay 12. Mai 2022 08:52

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
Hallo zusammen,

sorry, konnte mir das gestern nicht mehr genau ansehen - war ein bisschen knapp an Zeit...

Wenn ich mir jetzt das mit
Code:
Panel1.ControlsOf<TButton>
ansehe, dann ist das natürlich

1. absolut objektorientiert und
2. mit der Angabe der Control-Typen, die man haben will, auch noch praktischer als eine reine Auflistung.

Das werde ich auf jeden Fall nutzen!

Vielen Dank dafür!

Gruß

Freejay

Uwe Raabe 12. Mai 2022 09:03

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
Stefan Glienke hat gestern noch ein wenig Hand angelegt und ich möchte euch das Ergebnis nicht vorenthalten:

Zitat:

Zitat von Stevie
hab mal deinen control enumerator umgeschrieben, um jegliche allokationen zu vermeiden und alle methoden inlinen zu lassen

Delphi-Quellcode:
type
  TWinControlHelper = class helper for TWinControl
  type
    TControlEnumerator<T: TControl> = record
    private
      FIndex, FCount: Integer;
      FWinControl: TWinControl;
      FCurrent: T;
    public
      function MoveNext: Boolean; inline;
      property Current: T read FCurrent;
    end;

    TControls<T: TControl> = record
    private
      FWinControl: TWinControl;
    public
      function GetEnumerator: TControlEnumerator<T>; inline;
    end;
  public
    function ControlsOf<T: TControl>: TControls<T>; inline;
  end;

{ TWinControlHelper }

function TWinControlHelper.ControlsOf<T>: TControls<T>;
begin
  Result.FWinControl := Self;
end;

{ TWinControlHelper.TControls<T> }

function TWinControlHelper.TControls<T>.GetEnumerator: TControlEnumerator<T>;
begin
  Result.FIndex := 0;
  Result.FWinControl := FWinControl;
  Result.FCount := FWinControl.ControlCount;
end;

function TWinControlHelper.TControlEnumerator<T>.MoveNext: Boolean;
var
  LControl: TControl;
begin
  repeat
    if FIndex < FCount then
    begin
      LControl := FWinControl.Controls[FIndex];
      Inc(FIndex);
      if LControl.InheritsFrom(T) then
      begin
        FCurrent := T(LControl);
        Exit(True);
      end;
    end
    else
      Exit(False)
  until False;
end;

freejay 12. Mai 2022 09:27

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
:thumb:

Uwe Raabe 12. Mai 2022 12:41

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
So, hier noch eine generische Alternative wenn es auf Performance nicht ganz so ankommt (wegen der anonymen Methode). Kann man dann allerdings auch leicht in anderen Situationen einsetzen.
Delphi-Quellcode:
type
  TEnumWrapper<T: class> = record
  type
    TGetItemFunc = TFunc<Integer, TObject>;
    TEnumerator = record
    private
      FIndex: Integer;
      FCount: Integer;
      FCurrent: T;
      FGetItem: TGetItemFunc;
    public
      function MoveNext: Boolean; inline;
      property Current: T read FCurrent;
    end;
  private
    FCount: Integer;
    FGetItem: TGetItemFunc;
  public
    constructor Create(ACount: Integer; AGetItem: TGetItemFunc);
    function GetEnumerator: TEnumerator; inline;
  end;

type
  TComponentHelper = class helper for TComponent
  public
    function ComponentsOf<T: TComponent>: TEnumWrapper<T>; inline;
  end;

type
  TWinControlHelper = class helper for TWinControl
  public
    function ControlsOf<T: TControl>: TEnumWrapper<T>; inline;
  end;

implementation

{ TEnumWrapper<T> }

constructor TEnumWrapper<T>.Create(ACount: Integer; AGetItem: TGetItemFunc);
begin
  FCount := ACount;
  FGetItem := AGetItem;
end;

function TEnumWrapper<T>.GetEnumerator: TEnumerator;
begin
  Result.FCount := FCount;
  Result.FGetItem := FGetItem;
  Result.FIndex := -1;
end;

function TEnumWrapper<T>.TEnumerator.MoveNext: Boolean;
var
  cmp: TObject;
begin
  repeat
    Inc(FIndex);
    if FIndex < FCount then
    begin
      cmp := FGetItem(FIndex);
      if cmp.InheritsFrom(T) then
      begin
        FCurrent := T(cmp);
        Exit(True);
      end;
      Continue;
    end;
  until True;
  Result := False;
end;

{ TComponentHelper }

function TComponentHelper.ComponentsOf<T>: TEnumWrapper<T>;
begin
  Result := TEnumWrapper<T>.Create(ComponentCount,
    function(Index: Integer): TObject
    begin
      Result := Components[Index];
    end);
end;

{ TWinControlHelper }

function TWinControlHelper.ControlsOf<T>: TEnumWrapper<T>;
begin
  Result := TEnumWrapper<T>.Create(ControlCount,
    function(Index: Integer): TObject
    begin
      Result := Controls[Index];
    end);
end;

Benmik 13. Mai 2022 18:35

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
Tolle Arbeit und toll, dass ihr euch die ganze Mühe macht, muss man mal sagen. :thumb:

Uwe Raabe 16. Mai 2022 17:00

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
https://quality.embarcadero.com/browse/RSP-38237

freejay 18. Mai 2022 07:45

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1505905)

Super, danke!

Frickler 18. Mai 2022 08:32

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1505905)

Ist das eigentlich normal, dass das Einloggen dort immer erst im 5. bis 10. Versuch klappt?

"remember me" funktioniert auch nicht...

freejay 18. Mai 2022 08:35

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
Ich hatte das Problem noch nicht.

Heimlich 25. Mai 2022 12:00

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1505732)
Stefan Glienke hat gestern noch ein wenig Hand angelegt und ich möchte euch das Ergebnis nicht vorenthalten:

Zitat:

Zitat von Stevie
hab mal deinen control enumerator umgeschrieben, um jegliche allokationen zu vermeiden und alle methoden inlinen zu lassen

Delphi-Quellcode:
type
  TWinControlHelper = class helper for TWinControl
  type
    TControlEnumerator<T: TControl> = record
    private
      FIndex, FCount: Integer;
      FWinControl: TWinControl;
      FCurrent: T;
    public
      function MoveNext: Boolean; inline;
      property Current: T read FCurrent;
    end;

    TControls<T: TControl> = record
    private
      FWinControl: TWinControl;
    public
      function GetEnumerator: TControlEnumerator<T>; inline;
    end;
  public
    function ControlsOf<T: TControl>: TControls<T>; inline;
  end;

{ TWinControlHelper }

function TWinControlHelper.ControlsOf<T>: TControls<T>;
begin
  Result.FWinControl := Self;
end;

{ TWinControlHelper.TControls<T> }

function TWinControlHelper.TControls<T>.GetEnumerator: TControlEnumerator<T>;
begin
  Result.FIndex := 0;
  Result.FWinControl := FWinControl;
  Result.FCount := FWinControl.ControlCount;
end;

function TWinControlHelper.TControlEnumerator<T>.MoveNext: Boolean;
var
  LControl: TControl;
begin
  repeat
    if FIndex < FCount then
    begin
      LControl := FWinControl.Controls[FIndex];
      Inc(FIndex);
      if LControl.InheritsFrom(T) then
      begin
        FCurrent := T(LControl);
        Exit(True);
      end;
    end
    else
      Exit(False)
  until False;
end;


Wie sieht es denn bei folgender Konstellation aus?

Delphi-Quellcode:
for i := 0 to ComponentCount-1 do
if Components[i] is TButton then
  TButton(Components[i]).Caption := 'Hello World'
else
if Components[i] is TPageControl then
  TPageControl(Components[i]).ActivePageIndex := 0
else
if Components[i] is TEdit then
  TEdit(Components[i]).Font.Style := [fsBold];
Man bräuchte dann ja 3 Durchläufe, anstatt, wie im Beispiel zusehen, nur einen Durchlauf.

Uwe Raabe 25. Mai 2022 16:50

AW: .Controls und .Components nicht als Auflistung nutzbar?
 
Das ist dann halt eine andere Anforderung, für die man ja immer noch den Standard-Enumerator verwenden kann:
Delphi-Quellcode:
for var cmp in Self do begin
  if cmp is TButton then
    TButton(cmp).Caption := 'Hello World'
  else if cmp is TPageControl then
    TPageControl(cmp).ActivePageIndex := 0
  else if cmp is TEdit then
    TEdit(cmp).Font.Style := [fsBold];
end;
Eigentlich ist das aber ein Anwendungsfall für das Visitor Pattern:
Delphi-Quellcode:
type
  TMyVisitor = class(TVisitor) { Source für TVisitor siehe Link }
  public
    procedure VisitButton(Instance: TButton);
    procedure VisitPageControl(Instance: TPageControl);
    procedure VisitEdit(Instance: TEdit);
  end;

procedure TMyVisitor.VisitButton(Instance: TButton);
begin
  Instance.Caption := 'Hello World';
end;

procedure TMyVisitor.VisitEdit(Instance: TEdit);
begin
  Instance.Font.Style := [fsBold];
end;

procedure TMyVisitor.VisitPageControl(Instance: TPageControl);
begin
  Instance.ActivePageIndex := 0;
end;

procedure TForm40.Button1Click(Sender: TObject);
var
  visitor: TMyVisitor;
begin
  visitor := TMyVisitor.Create;
  try
    for var cmp in Self do
      visitor.Visit(cmp);
  finally
    visitor.Free;
  end;
end;


Alle Zeitangaben in WEZ +1. Es ist jetzt 18:59 Uhr.

Powered by vBulletin® Copyright ©2000 - 2022, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2021 by Daniel R. Wolf