鳥の巣箱

ネトゲしたり、機械いじったり、ソフト書いたり、山篭ったり、ギャンブルしたりする人

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

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に対して配列の添え字最小値を加算するようにしてやる必要があります。
これを忘れると崩壊するので気を付けて使いましょう。