Delphi – adding for … in support to TPageControl
Posted by jpluimers on 2009/04/22
A long time ago, the for ... in
statement was added to the structured statements part of the Delphi language. It is a really powerful statement, that allows for code like this:
var Line: string; begin for Line in MyMemo.Lines do // some business logic end;
in stead of using the traditional for
statement which needs an extra LineIndex variable and an assignment statement:
var LineIndex: Integer; Line: string; begin for LineIndex := 0 to MyMemo.Lines.Count do begin Line := MyMemo.Lines[LineIndex]; // some business logic end; end;
So, “for … in” is a cool feature, but now I wanted to do the same for a TPageControl:
procedure TContactLensRadiiExpressionsForm.FormCreate(Sender: TObject); var TabSheet: TTabSheet; begin for TabSheet in RadiiPageControl do TabSheet.TabVisible := False; end;
In order to do that, TPageControl
needs a function called GetEnumerator
which returns an instance of an enumerator type that assists the for ... in
(lets call it TPageControlEnumerator
).
But: TPageControl
is part of the VCL, and it is a no-no to change the VCL yourself.
(A long time ago, I was at a client where a programmer thought to be smart and extended the VCL by changing the Delphi VCL files; that effectively made it a tour-de-force to upgrade to a more recent Delphi version).
But, wait: in the same time frame (the DCCIL .NET commandline compiler preview), another feature was added to the Delphi language as well: class helpers (which now have been extended to record helpers and both have been back-ported to the Win32 compiler in Delphi 2005).
And this helps!
I have been a long-time fan of class helpers (especially because it allowed me to develop .NET Compact Framework applications using Delphi for .NET), and they come in really handy this time as well.
Complete code is at the end of this blog post, but first lets explain the fundamentals:
The TPageControlHelper implements a GetEnumerator function which returns an instance of the actual class that does the enumeration: TPageControlEnumerator.
Then TPageControlEnumerator implements a Current property and MoveNext method. And of course a Create constructor:
type TPageControlEnumerator = class(TObject) public constructor Create(const FPageControl: TPageControl); function GetCurrent: TTabSheet; function MoveNext: Boolean; property Current: TTabSheet read GetCurrent; end; TPageControlHelper = class helper for TPageControl public function GetEnumerator: TPageControlEnumerator; end;
The important thing to remember here is that the TPageControlEnumerator instance must be initialized to the state before the first call of MoveNext.
In this case, this means we keep an FValue variable that is initialized one less than FMinValue. Technically, FMinValue is not needed, but it is easier for debugging purposes.
MoveNext then increases FValue and returns a boolean indicating if it was successful.
The rest then is just filling in the blanks.
Usage is easy: just add “PageControlHelperUnit” to your uses list, and you can use “for … in” on TPageControls.
This idea is of course straight forward to implement in other places as well (command-line parameters anyone?).
unit PageControlHelperUnit; interface uses ComCtrls; type TPageControlEnumerator = class(TObject) strict private FMinValue: Integer; FMaxValue: Integer; FValue: Integer; FPageControl: TPageControl; public constructor Create(const FPageControl: TPageControl); destructor Destroy; override; function GetCurrent: TTabSheet; function MoveNext: Boolean; property Current: TTabSheet read GetCurrent; end; TPageControlHelper = class helper for TPageControl public function GetEnumerator: TPageControlEnumerator; end; implementation { TPageControlHelper } function TPageControlHelper.GetEnumerator: TPageControlEnumerator; begin Result := TPageControlEnumerator.Create(Self); end; { TPageControlEnumerator } constructor TPageControlEnumerator.Create(const FPageControl: TPageControl); begin Assert(Assigned(FPageControl), 'cannot enumerate nil FPageControl'); inherited Create(); Self.FPageControl := FPageControl; FMinValue := 0; FMaxValue := FPageControl.PageCount - 1; FValue := FMinValue - 1; end; destructor TPageControlEnumerator.Destroy; begin inherited Destroy(); end; function TPageControlEnumerator.GetCurrent: TTabSheet; begin Result := FPageControl.Pages[FValue]; end; function TPageControlEnumerator.MoveNext: Boolean; begin Inc(FValue); Result := FValue <= FMaxValue; end; end.
Delphi - class helper to add for … in support for TComponent.Components/ComponentCount « The Wiert Corner said
[…] is another case where I’d like to use the for … in statement: accessing the Components of a TComponent […]