複数のコンポーネントを配列化して処理する~イベント編~

Delphi,FMX,Programing,VCL

birdhouse.hateblo.jp

以前、Delphiで複数のコンポーネントを配列にまとめて、一括処理する方法について書きましたが
今回はそのコンポーネントイベントも一括で書いてしまおうという内容です。

イベントを一括で記述する方法

今回は複数のTButtonを配列にして、OnClickイベントの処理を一括で書いてしまいます。
コードはこんな感じ。

unit Unit2;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
System.Generics.Collections,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
FMX.Controls.Presentation, FMX.Edit, FMX.StdCtrls;
type
TForm2 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
Button5: TButton;
Label1: TLabel;
procedure FormCreate(Sender: TObject);
procedure ButtonClick(Sender: TObject);
private
{ private 宣言 }
public
{ public 宣言 }
Button  : Array[1..5] of TButton;
end;
var
Form2: TForm2;
implementation
{$R *.fmx}
procedure TForm2.ButtonClick(Sender: TObject);
var
index : Integer;
begin
TButton(Sender).Text := 'My Click!!';
end;
procedure TForm2.FormCreate(Sender: TObject);
var
I: Integer;
begin
for I := Low(Button) to
High(Button) do
begin
Button[I] := TButton(FindComponent('Button'+IntToStr(I)));
Button[I].OnClick := ButtonClick;
end;
end;
end.

FormCreateでTButtonの配列に各コンポーネントを代入しています。
ここは前回と同様です。
その時に今回はOnClickプロパティにButtonClickを指定しています。
OnClickプロパティの型はTNotifyEventです。

  TNotifyEvent = procedure(Sender: TObject) of
object;

なのでButtonClickの宣言を合わせています。

こうすることで、配列中のどれかボタンがクリックされると、ButtonClickがコールされることになります。

さて、問題は配列中のどのボタンが呼び出したのかという事です。

ここで使うのがSenderです。
Senderには関数を呼び出したオブジェクト自身が渡されています。
なので、これをTButtonでキャストすることでそのまま使うことができます。

  TButton(Sender).Text := 'My Click!!';

ここで、クリックされたボタンのTextプロパティが変更されます。

呼び出し元の配列インデックスを取得したい場合

さて、呼び出したオブジェクト自身を操作することはできましたが、呼び出したオブジェクトが配列の何番目なのかを知りたい。という場面も多々あると思います。
例えば、配列のインデックスを取得してその番号をTLabelなどで表示したい、という時。

これには2パターンのやり方があります。

パターン1 for文で回す

procedure TForm2.ButtonClick(Sender: TObject);
var
index : Integer;
begin
for index := Low(Button) to
High(Button) do
begin
if Button[index] = TButton(Sender) then
begin
Label1.Text := 'Button'+IntToStr(index)+' OnClick';
Break;
end;
end;
end;

TButton配列を順番に確認していって、Senderと一致した場合にその時のIndexを使って処理をする。
というやり方です。
このやり方は非常に単純ですが、処理が冗長になりやすいのと、forやifでコードのネストが深くなりがちなので読みにくくなります。

パターン2 TArray.BinarySearchを使う

procedure TForm2.ButtonClick(Sender: TObject);
var
index : Integer;
begin
TArray.BinarySearch<TButton>(Button, TButton(Sender), index);
Label1.Text := 'Button'+IntToStr(index)+' OnClick';
end;

System.Generics.CollectionsのTArray.BinarySearchを使う方法です。
パラメータに配列と、要素、見つけたindexを格納する変数を与えます。

このやり方の良いところは、圧倒的にコード量が少なく済みます。

ですが、問題はあります。
上記のコード例だと、実はindexが1ずつずれるんですね。
TArray.BinarySearchはindexが「0」から開始することを前提に返してくるので
今回のように、indexが「1」から始まるような静的配列だと、本来「1」を返してほしいのに「0」が返ってくることになります。
宣言時点でのindexの最小値分だけずれるわけです。

なのでこの場合だと、

procedure TForm2.ButtonClick(Sender: TObject);
var
index : Integer;
begin
TArray.BinarySearch<TButton>(Button, TButton(Sender), index);
index := index+Low(Button);
Label1.Text := 'Button'+IntToStr(index)+' OnClick';
end;

のように、戻ってきたindexに対して配列の添え字最小値を加算するようにしてやる必要があります。
これを忘れると崩壊するので気を付けて使いましょう。