Delphi Vclでカスタムコンポーネントに、独自設定のイベントを付けるメモ

# お題
Delphi VCL で カスタムコンポーネントをつくっていると、独自のイベントを作りたくなりますよね？　ね？　ね？
イベントを発生したら、イベントハンドラに色々とパラメータを渡ししたいですよね？
今回は、イベント型をつくって、使う方法のメモになります。

# 準備
今回もコンポーネントをインストールしないで試します。
なので、まず、親になるVCLアプリケーションを作ります。

ファイル > 新規作成 > Windows VCLアプリケーション でアプリを作ります。
のっぺらぼーのフォームが表示されます。
テストデータを表示するため、Panelをひとつだけ貼り付けてください。

| 1 | 2 |
|:-|:-|
|フォームの名前  |Form1     |
|ファイルの名前  |Unit1.pas |

こんな感じで、適当なフォルダーに保存。

コンポーネント > コンポーネントの新規作成 から新規コンポーネントを作ってください。
コンポーネントの派生元は、TCustomControl　を選択します。
新規に作るコンポーネントの名前は、とりあえず TCustomEvCpt にしています。

| 1 | 2 |
|:-|:-|
|派生元|TCustomControl|
|新規コンポーネントの名前  |TCustomEvCpt  |
|ファイルの名前  |CustomEvCpt.pas |

インストールするを選択しません。
新規作成を選択します。
フォームと、同じフォルダーに保存します。



# コンポーネント側
まずコンポーネント側から始めます。
 
## イベント型を宣言します。
T○○○○Event = procedure (Sender:TObject; var 渡すパラメーター :型) of Object;
と、いう書き方になります。
Sender:TObject　は、必要です。これがないと、あとでメインフォームから呼び出そうとしたときに、パラメーター不一致でエラーになっちゃいますから。

```pascal
type  //イベント型   イベント ハンドラ手続きを指すメソッド ポインタ★
  TShikenEvent = procedure (Sender:TObject; var num1,num2:Longint) of object;

```
## カスタムコンポーネントを宣言します。

```pascal
type
  TCustomEvCpt = class(TCustomControl)
  private
    { Private 宣言 }
    FOnTestEvent:TShikenEvent; //Forceを選択するイベント型のフィールド★

  protected
    { Protected 宣言 }
  public
    { Public 宣言 }
    procedure Paint; override;  //見えないとマウスでポチれないからね
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;

  published
    { Published 宣言 }
    property OnMouseDown;     //イベント発生の場所として設定
    property OnTestEvent:TShikenEvent read FOnTestEvent write FOnTestEvent; 
  end;
```

private 宣言のところに、イベント型のポインタを保持するフィールドを宣言します。

FOn◇◇◇Event : T○○○○Event;
という書き方になります。
イベントと、さっき作ったイベント型を結びつけます。


## イベントを宣言します。
published 宣言に property としてイベントを宣言します。
イベントって、実は、property なんです。
property On◇◇◇Event : T○○○○Event read FOn◇◇◇Event write On◇◇◇Event;
つまり
property Onイベントの名前 : イベント型 read Fonフィールド write Fonフィールド;
という書き方になります。

```pascal
  published
    { Published 宣言 }
    property OnTestEvent:TShikenEvent read FOnTestEvent write FOnTestEvent; 
  end;
```

## 実装部
コンポーネント側で実際に、イベントを発生させる処理です。
ポイントは、

if Assigned(FOnTestEvent) then 
と、評価してから、
OnTestEvent(Self,Dat1,Dat2);
と、イベント発生を行うこと。
Assigned( )は、ポインタ変数や手続き変数が nil（代入されていない）かどうかを検査します。 

なぜ、検査する必要があるかというと……
メインフォーム側でイベントハンドラを用意しているとは、限らないからです。
if Assigned(FOnTestEvent) then の値は、メインフォーム側がイベントハンドラを使っていないときは、False になります。

```pascal
procedure TCustomEvCpt.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Assigned(FOnTestEvent) then //イベント発生
    OnTestEvent(self,x,y);

  inherited;
end;
```

Senderには、Selfを入れます。
作例として、Mouse座標 X,Y をイベントハンドラへ渡しています。
以上で、コンポーネント側の準備は完了です。
つぎは、呼び出して使う側のメインフォームでの処理になります。


# メインフォーム側

## uses節に書き足し
まず、先ほど作ったカスタムコンポーネントを、Uses節の末尾に書き足します。
とりあえず「CustomEvCpt.pas」というファイル名で、同じフォルダーの中に入れたのでこうなります。
拡張子のPasは省略です。
PATHが通すの、めんどくさいときは、とりあえず同じフォルダにファイルを保存で良いと思います。
```pascal
uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics,  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls,CustomEvCpt;
```

## カスタムコンポーネントと独自イベントを宣言

```pascal
type
  TForm1 = class(TForm)
    Panel1: TPanel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private 宣言 }
    Nanika:TCustomEvCpt;
  public
    { Public 宣言 }
    procedure TestEvent (Sender:TObject; var num1,num2:Longint);
  end;

```

## コンポーネントを作って、使って、ポイ
イベントも、コンポーネントを作成時に、一緒に定義します。
TCustomEvCptクラスのコンポーネントで、名前は Nanika です。

```pascal
procedure TForm1.FormCreate(Sender: TObject);
begin
  Nanika:=TCustomEvCpt.Create(Self);  //コンポーネント作成

  with Nanika do
  begin
    parent:=self; //コンポーネントには親が必要です
    left:=0;
    top:=0;
    width:=100;
    height:=100;
    align:=alClient;
    OnTestEvent:=TestEvent; //イベントを宣言★
  end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Nanika.free;
end;

```

以上で、カスタムコンポーネントを作って、独自イベントを発生させる準備が整いました。
使うときは、ふつうにこんな風にします。

```pascal
procedure TForm1.TestEvent (Sender:TObject; var num1,num2:Longint);
begin
  Panel1.caption:='Mouse X:'+inttostr(num1)+'  Y:'+inttostr(num2);
end;
```

# 実行します
[F9]キーでコンパイルして、実行します。
赤い枠で、コンポーネントが表示されているため、マウスでポチって下さい。
Panle1に、mouse 101 202 と、テスト用のデータが表示されます。

# 図にまとめてみます
![イベント図解1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273793/dd9eadbb-e23c-4067-b5e7-a51f1f920fbf.png)


![イベント図解2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273793/71057b3c-1718-4c52-9b0e-be2b78994b6a.png)


# あとがき
Senderを使わないから、これを省略したら、
[dcc32 エラー] Unit1.pas(41): E2009 型に互換性がありません : パラメータリストが異なります。
って、コンパイラさんに怒られて、原因がわからず、ネットを探し回るハメになりました。

OnTestEvent:=TestEvent;
ここで、文字列の上にマウスを載せると、パラメーターが表示されます。
左辺の OnTestEvent が、コンポーネント側で、
右辺の TestEvent　 が、メインフォーム側です。

何気なく、マウスを載せたら……　あれ？　右辺にだけSenderがあるって、気づいたのでした。
苦労したので、めもめも。

参考

dowiki の System.Assigned　の説明ページ
https://docwiki.embarcadero.com/Libraries/Sydney/ja/System.Assigned




### プログラム全文を乗せます。

▼▼▼　クリックして　開いてください　▼▼▼


<details><summary>Unit1.pas　プログラム全文　[クリックで開いてください]</summary><div>

```pascal
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls,CustomEvCpt;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private 宣言 }
    Nanika:TCustomEvCpt;
  public
    { Public 宣言 }
    procedure TestEvent (Sender:TObject; var num1,num2:Longint);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  Nanika:=TCustomEvCpt.Create(Self);

  with Nanika do
  begin
    parent:=self;
    left:=0;
    top:=0;
    width:=100;
    height:=100;
    align:=alClient;
    OnTestEvent:=TestEvent;
  end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Nanika.free;
end;

procedure TForm1.TestEvent (Sender:TObject; var num1,num2:Longint);
begin
  Panel1.caption:='Mouse X:'+inttostr(num1)+'  Y:'+inttostr(num2);
end;

end.
```

</div></details>




<details><summary>CustomEv.pas　プログラム全文　[クリックで開いてください]</summary><div>

```pascal
unit CustomEvCpt;
//カスタムコンポーネントに独自イベントを設定する方法

interface

uses
  System.SysUtils, System.Classes, Vcl.Controls,Vcl.Graphics;

type  //イベント型   イベント ハンドラ手続きを指すメソッド ポインタ★
  TShikenEvent = procedure (Sender:TObject; var num1,num2:Longint) of object;
//  TShikenEvent = procedure (var num1,num2:Longint) of object;
                                 //Sender:TObjectは必要です。パラメーター不一致になるから。
type
  TCustomEvCpt = class(TCustomControl)
  private
    { Private 宣言 }
    FOnTestEvent:TShikenEvent; //Forceを選択するイベント型のフィールド★

  protected
    { Protected 宣言 }
  public
    { Public 宣言 }
    procedure Paint; override;  //見えないとマウスでポチれないからね
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;

  published
    { Published 宣言 }
    property OnMouseDown;     //イベント発生の場所として設定
    property OnTestEvent:TShikenEvent read FOnTestEvent write FOnTestEvent;  //★フィールドです
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TCustomEvCpt]);
end;

procedure TCustomEvCpt.Paint;
begin
  with canvas do
  begin
    pen.color:=clRed;                   //MouseDownでイベント発生にしているため
    rectangle(left,top,width,height);   //ポチるために、コンポーネントを描きます
  end;

  inherited;
end;

procedure TCustomEvCpt.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Assigned(FOnTestEvent) then //イベント発生
    OnTestEvent(self,x,y);

  inherited;
end;

end.

```

</div></details>