Thursday, June 19, 2014

Enumerating Controls the Easy Way

Yesterday I had to write a mapping from a set of radio buttons to an integer (and back). I couldn’t use TRadioGroup and so I had to iterate over controls to find out which one is checked. To do that, I used an enumeration helper which I wrote long time ago.

function TfrmColorRemappingDlg.GetMapping: integer;
var
ctrl:
TControl;
begin
Result := 0
;
for ctrl in EnumControls(Self, TRadioButton)
do
if TRadioButton(ctrl).Checked
then
Exit(ctrl.Tag);
end;

At that point I decided that this old-way enumeration is not good enough for the modern times. What I particularly dislike about it is that I know the type of objects enumerator will return (TRadioButton in this example) but I still have to cast the enumeration variable to that type. The solution is simple – use generics!


function TfrmColorRemappingDlg.GetMapping: integer;
var
ctrl:
TRadioButton;
begin
Result := 0
;
for ctrl in EnumControls<TRadioButton>
do
if ctrl.Checked
then
Exit(ctrl.Tag);
end;

Much nicer.


I have implemented new enumerator factory as a class helper for TWinControl. This helper returns a record which is at the same time an enumerator factory (it implements a GetEnumerator function) and an enumerator (implementing [Get]Current and MoveNext).

type
TControlEnumeratorFactory<T:TControl> =
record
strict
private
FIndex :
integer;
FParent:
TWinControl;
public
constructor Create(parent:
TWinControl);
function GetCurrent:
T;
function GetEnumerator:
TControlEnumeratorFactory<T>;
function MoveNext:
boolean;
property Current: T read
GetCurrent;
end
;
  TWinControlEnumerator = class helper for TWinControl
public
function EnumControls: TControlEnumeratorFactory<TControl>; overload
;
function EnumControls<T:TControl>: TControlEnumeratorFactory<T>; overload
;
end
;

The implementation is pretty simple.

constructor TControlEnumeratorFactory<T>.Create(parent: TWinControl);
begin
FParent :=
parent;
FIndex := -1
;
end;


function TControlEnumeratorFactory<T>.GetCurrent: T;
begin
Result :=
T(FParent.Controls[FIndex]);
end;


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


function TControlEnumeratorFactory<T>.MoveNext: boolean;
begin
Result :=
false;
while FIndex < (FParent.ControlCount - 1) do
begin
Inc(FIndex);
if FParent.Controls[FIndex].InheritsFrom(T) then
begin
Result :=
true;
break;
//while
end
;
end;
//while
end;

All this (and more) is available in the open-sourced GpVCL unit.

No comments:

Post a Comment