Updownボタンのクリックに反応するイベントを作成する – Delphiカスタムコンポーネントの作成 15

投稿者: | 2018年4月21日

イベントとは

イベントを作成する前に、イベントについて少し説明します。
たとえばボタンをクリックすると、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 イベントが発生することがありますので、使用には注意が必要です。

ADs
  

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)