イベントとは
イベントを作成する前に、イベントについて少し説明します。
たとえばボタンをクリックすると、OnClickというイベントが発生します。OnClickにイベントハンドラーが設定されていれば、そのイベントハンドラーが実行されます。Delphiでプログラムを作成されている方であれば、ここを詳しく説明しなくても分かると思います。
では、コンポーネント内部ではどのように実装されているのでしょうか。
イベントはプロパティである
イベントは、プロパティの一種です。コンポーネントでの実装から見れば、プロパティもイベントも大して変わりありません。
プロパティとイベントの何が違うのかというと、その型です。プロパティは数値や文字列などのデータを扱います。イベントは手続き(procedure)を扱います。言い方を変えると、イベントは手続きをデータに持つプロパティです。イベントに設定された手続きが、イベントハンドラーと呼ばれます。
関数(function)もイベントの型にできますが、推奨されません。
イベントのデータ型は、メソッドポインタ型になります。通常の手続きではなく、クラスのメソッドがイベントハンドラーとして使用できます。
イベントの発生手順
イベントに設定したイベントハンドラーは、勝手に実行されるわけではありません。適切なタイミングでイベントハンドラーを実行するのは、コンポーネントの仕事です。
たとえば、ボタンのOnClickイベントの場合、何らかの方法でボタンがクリックされたことを検知します。ボタンがクリックされたことが分かると、OnClickイベントにイベントハンドラーがあるか確認します。イベントハンドラーが設定されていれば、それを実行します。
OnUpdownClickイベントの作成
実際に、イベントを一つ作成します。Updownボタンがクリックされたら、OnUpdownClickイベントを発生させるようにします。
FOnUpdownClickフィールドの追加
イベントにはイベントハンドラーを設定できます。このイベントハンドラーを記憶しておくためのフィールド(変数)が必要です。OnUpdownClickイベントのフィールドと分かるように、フィールド名はイベント名の先頭に F を付けたものにします。つまり、FOnUpdownClick というフィールド名になります。以下のように、TCustomNumberEditクラスの private 部に、FOnUpdownClickフィールドを追加します(24行目)。
TCustomNumberEdit = class(TCustomEdit)
private
{ Private 宣言 }
FComma: Boolean;
FDigits: Integer;
FIncrement: Extended;
FOnUpdownClick: TNotifyEvent;
FOnUpdownClickフィールドの型は、TNotifyEvent型です。TNotifyEvent型とは、引数がないイベントの基本の型です。TButtonの OnClick イベントや、TEditの OnChange イベントなど、多くのイベントで使われている型です。
なお、引数がないと言っても、実際には Sender という引数だけは存在します。正確に書けば、Sender引数だけ存在し、追加の引数がないイベントの型になります。
OnUpdownClickイベントの追加
イベントはプロパティであると前に述べました。イベントの定義は下記のように、property として記述します。TCustomNumberEditクラスの public 部で定義しましょう(55-56行目)。
public
{ Public 宣言 }
property Comma: Boolean read FComma write SetComma;
property Digits: Integer read FDigits write SetDigits;
property Value: Extended read FValue write SetValue;
property OnUpdownClick: TNotifyEvent read FOnUpdownClick
write FOnUpdownClick;
イベントはread、writeの両方とも、フィールドを指定する必要があります。GetUpdownClick、SetUpdownClick のようなメソッドを指定できませんので、覚えておいてください。
イベントを発生させるメソッドの作成
イベントを発生させるためのメソッドを作成します。イベントを発生させるメソッドは、protected スコープで、仮想メソッド(virtual)、または動的メソッド(dynamic)として作成することをおすすめします。そうすれば下位クラスでオーバーライドでき、イベント発生時に追加処理を実行できるようになります。
以下のように、UpdownClickという名前で、動的メソッドを作成します(47行目)。
function TextToValue(const S: string): Extended; virtual;
procedure UpdownChanging(Sender: TObject; var AllowChange: Boolean;
NewValue: Integer; Direction: TUpDownDirection);
procedure UpdownClick; dynamic;
実装は以下のとおりです。
procedure TCustomNumberEdit.UpdownClick;
begin
if Assigned(FOnUpdownClick) then FOnUpdownClick(Self);
end;
イベントハンドラーを実行するには、まず、フィールドにイベントハンドラーが設定されているかを確認します。Assigned関数は、引数が nil かどうか確認し、nilでなければ True を返します。
FOnUpdownClick はメソッドポインタ型(メソッドへのポインター)ですので、イベントハンドラーが設定されていればそのポインター、設定されていなければ nil になります。したがって、Assigned の結果が True なら、イベントハンドラーが設定されています。
if 文が True であれば、FOnUpdownClickフィールドに設定されているイベントハンドラーを実行します。イベントハンドラーの実行は、FOnUpdownClick(引数); と書くだけです。
引数は Sender だけです。Sender 引数は、イベントが発生したコンポーネント(のインスタンス)を指します。イベントは今、自分が発生させているので、引数には Self を指定します。Self は特別な変数で、自分自身(のインスタンス)を表します。
イベントを発生させる
Updownボタンがクリックされたときに上記のメソッドを実行して、イベントを発生させます。
以下のように、UpdownChangingメソッドを修正します(397行目を追加)。
procedure TCustomNumberEdit.UpdownChanging(Sender: TObject;
var AllowChange: Boolean; NewValue: Integer; Direction: TUpDownDirection);
begin
// 数値のインクリメント・デクリメント
case Direction of
TUpDownDirection.updUp:
Value := Value + FIncrement;
else
Value := Value - FIncrement;
end;
// TCustomUpDownのPosition変更の抑制
AllowChange := False;
// OnUpdownClickイベント生成
UpdownClick;
end;
これで、Updownボタンをクリックする度、OnUpdownClickイベントが発生するようになりました。
TCustomUpDownには不具合があります。少なくとも、Delphi 10.2 Tokyo の バージョン 25.0.26309.314 では、OnClick、OnChangingEx イベント部分に不具合があります。
フォームの閉じる(×)をクリックしたり、フォームが非アクティブになろうとしたりしたときに、UpDown ボタンの OnClick イベント等が発生してしまいます。もし、OnClick イベントなどにダイアログを表示する処理を書いていると、フォームを閉じようとしたときにダイアログが表示され、フォームのクローズがキャンセルされます。これを回避するにはアプリケーション側で対策するしかありません。
そのため、今回追加した OnUpdownClick イベントも、UpDownボタンをクリックしていないのに OnUpdownClick イベントが発生することがありますので、使用には注意が必要です。