Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2007年11月10日] [更新 2015年07月21日] Delphi 一般・その他
イベントの作成方法 ( 入門者用 )
動作確認等 Windows 7 U64(SP1) + Delphi 2010 Pro
[整理番号 - 0110]



  • 2013年10月26日
  • Delphi 2010 を使用した記事と画像に変更
  • 2015年07月20日
  • [イベントはメソッドとして使用できる] 以降の記事を追加




01_プロパティや関数・手続きとイベント


プロパティはプログラミングだけでなくパソコンの用語としてもよく使用するそれと同じです。
関数は、数学の「関数」と同じで、引数があり (なしものあります)、その値を使用して何かの結果を出す、それは印刷かも知れませんし、計算かも知れません。あるいは画面への表示かも知れません。関数のような戻り値がない処理を手続きと言っています。

イベントは日常生活でよく使用する「○○のイベント」とほとんど同じで、何かを起こす、何かが起きるぐらいの意味で十分でしょう。例えば、「ボタンクリックのイベントが発生したら、色などのプロパティを変更する」あるいは「ボタンクリックのイベントが発生したら、ある関数を実行する」ということになります。
ここでは、「ボタンをクリックしたら、ダイアログを表示する関数を実行する」というコードを作成してみます。また、イベントの挙動とメッセージの関係についてもテストしています。

このページの説明はあくまでも「入門者用」としての内容です。XXX してはいけない。とい文章がありますが、理解した上での変更が可能な機能もあります。事実、「特別な領域」を直接編集する説明がこのサイトの記事にもあります。
これは決して矛盾ではありません。Delphi や IDE の理解が十分でない時期に操作してしまうのは、混乱の元となります。





02_新規にプロジェクトを作成する


Delphi は管理者権限 (管理者として実行) が必要なアプリケーションです。Delphi (の IDE) は管理者権限で起動します。
下図は Delphi 2010 を管理者権限で起動し、メニューの [ファイル] [新規作成] [VCL フォームアプリケーション] を選択した時の画面です。これは「総合開発環境」、通称「IDE (アイ・ディー・イー)」と言われている画面です。IDEは、Integrated Development Environment の略です。イベントは、この画面の [オブジェクト・インスペクタ] を使用して作成します。

Delphi の IDE を起動した時に、初期状態で VCL フォームアプリケーションの開発状態にすることも、昔の Delphi の IDE のように、上部のツールバーに、コンポーネントパレットを表示するようにすることもできます。


  • 図1
  • Delphi 2010 を起動して、 [VCL フォームアプリケーション] にした画面
  • これを IDE 画面あるいは IDE の画面ともいう

IDE の最下部にある [コード] タブをクリックするか、[F12] キーを押下すると、下図のようなコードエディタの画面となります。


  • 図2
  • コードエディタの画面




03_プロジェクトを保存する - 保存場所に注意


作成したプロジェクトを保存しておくことにします。

[ファイル] [プロジェクトに名前を付けて保存] で保存のダイアログを表示します。最初はユニットファイルの保存で、次がプロジェクトファイルの保存となります。
表示された保存ダイアログでそのまま保存してしまうと、デフォルトの保存先に上書き保存となってしまいます。保存先は、新規にフォルダを作成するか、既存の別のフォルダとします。当然ですが、システム関係のフォルダの階層下や [Program Files] ([Program Files (x86)]) 等の階層下には保存しないようにします。必要に応じて保存の時にユニットとプロジェクトの名前を変更します。拡張子は変更しないでください。

一度プロジェクトを保存すれば、後はフロッピーディスクのアイコンをクリックすることで、その後に修正した内容を上書き保存できます。



図3
新規に作成したプロジェクトを保存する
  • 最初に表示されるフォルダは IDE のデフォルトの保存先。原則としてそこには保存しないようにする ([プロジェクトのデフォルトの保存先] の記事を参照)
  • 最初の保存ダイアログではユニットの保存となる
  • 次の保存ダイアログでプロジェクトファイルの保存となる
  • 共に拡張子は変更しないこと




04_ボタンを配置して、ボタンクリックのイベントを作成


まず、ボタンをフォームに配置します。
上の IDE 画面の下の方の [コード] [デザイン] のタブの内、[デザイン] タブをクリックします。すると、フォームが表示されます。この状態で、IDE 画面右側の方のツールパレットから TButton を選択します。選択したら、フォームをマウスでクリックします。すると、フォームにボタンが配置されます。


図4
ツールパレットでボタンを選択
  • [Standard] の [+] をクリックして展開
  • TButton を選択
図5
フォームをクリック
  • フォームの何もないところをクリック
図6
ボタンが配置される
  • 名前は Button1 となる
  • ボタンはいくつでも配置できる
  • もう1つ配置すると Button2 となる
  • さらに配置すると Button3 となる

ボタンというオブジェクトは、目に見えています。このようなオブジェクト、コントロールを Delphi ではコンポーネントと言っています。

ツールパレットからボタンを選択する時に、TButton というのを選びました。この TButton という名前はクラス名、コンポーネントのクラス名と言います。TButton というクラス名のオブジェクトをフォームに配置すると、Button1, Button2, Button2,... という名前のボタンができます。

これらの Button1, Button2, Button2,... は、TButton というオブジェクトから作成された、そのオブジェクトの実体、インスタンスです。単にボタンのオブジェクトと書くこともありますが、それがインスタンスを意味するのか、それとも実体が生成されていないオブジェクトを意味するのかは、説明文やコードで判断します。

このコンポーネントのインスタンスの名前もまた、単にコンポーネント名と言うことがあります。このコンポーネント名は、オブジェクトインスペクタの Name プロパティで変更することができます。

次に本題の、ボタンをクリックした時のイベントを作成します。下図の手順で作成します。
イベントとは何かが発生することですから、「イベントの作成」と言うのは、実際にはイベントが発生した時に実行する手続きを作成することです。


図7
ボタンを選択
図8
イベント作成
  • [オブジェクト・インスペクタ] の [イベント] タブをクリック
  • OnClick の右の空白欄をダブルクリック

上の作業の結果、コードは次のようになります。
オブジェクトインスペクタの [イベント] の種類は OnClick となっていますが、出来たコード上での名前は、デフォルトでは、上の図の、オブジェクトインスペクタのように、Button1Click という名前得になっています。これは Delphi が自動的に付ける名前です。

多くのイベントがありますが、これらはコンポーネント (コントロール) によって違います。どのイベントがどんな動作をするのかは、ヘルプ等で確認することになります。



  • 図9
  • ボタンを 1 つフォームに配置して、OnClick イベントを作成したコード
  • 先頭が // の行はコメントで、筆者が追加したもの
  • この状態で保存しない。保存すると作成されたコードが削除されてしまう

[備考]
自作のメソッドの宣言は、特別な領域には書きません。開発者が定義して使用するメソッドは、private部 や public 部に書きます。そうすれば、オブジェクトインスペクタで設定したイベント (もメソッドの一種です) と区別できます。
また、他のユニットから、それらのメソッド類を参照する際のアクセス制限も可能となります。このような慣習には従った方が無難です。




05_イベント発生時のコード ( イベントハンドラ ) を作成して実行


次に、ボタンクリックのイベントに、ボタンをクリックした時に実行するコードを書きます。
ここでは、ShowMessage という関数を用いて、ダイアログを表示します。そのために、Button1Click の実装部に次のようにコードを記述します (キーボードから入力します)。


リスト1 
procedure TForm1.Button1Click(Sender: TObject);
begin
   ShowMessage('こんにちは');  //これを記述する
end;


ShowMessage という関数をキーボードから入力する時に、下図のような入力支援機能を利用するとタイプミスを防ぐことができます。
逆に、この入力支援機能でリストに現れない関数やコードは、起動中の、使用している  Delphiのバージョンでは利用できないということになります。


図10
入力支援機能が使える
  • [Ctrl] + [Space] を押す
  • 文字 S をタイプ
  • 続いて H, O ... をタイプ
  • リストから ShowMessage を選択

入力が終わったら、メニューの [ファイル] [プロジェクトに名前を付けて保存] で保存します。
保存したら [F9] キーを押して実行します。
[F9] はプロジェクトをコンパイルして EXE ファイルを作成し、更に作成した EXE を起動します。[F9] の代わりに、下図のようなツールボタンやメニューからも実行できます。


図11
ボタンをクリックして実行
  • Delphi 2010 の場合
図12
Delphi XE 以降での [実行] ボタン
  • [デバッガを使わずに実行] と、デバッガを使用する [実行] のボタンがある
  • 通常はデバッガを使用する [実行] ボタンでよい

[F9] キーを押すとコンパイルされて、直ちにプログラムが実行されます。フォームが表示されたらマウスでボタンをクリックします。
下図のように、ダイアログが現れます。
これで、ボタンをクリックした時のイベントの作成は終了です。他のイベント、他のコンポーネント、コントロールとも言いますが、それらのイベントの作成方法も同じです。重要なことは、イベントは、必ず、オブジェクトインスペクタを使用して作成するということです。


図13
実行
  • [F9] でコンパイルして実行
  • ボタンをクリック


[備考]
[ツール] [オプション] のダイアログで、 [環境オプション] の [実行時に最小化] の項目をチェックしておくと、コンパイルして実行した時に、IDE が最小化表示になります。EXE を単独起動した時のデスクトップでの表示と同じになります。





06_イベントはウィンドウズのメッセージ処理


デスクトップのアイコンをクリックしたり、上のプログラムの様にボタンをクリックしたり、何かが起るとメッセージが発生します。Windows はこのメッセージを常に監視し、メッセージの内容に応じて必要な処理を行います。通常このメッセージは、アプリケーションでは捕まえること (捕捉) はできません。
Delphi は、このメッセージを捕捉する仕組みとして「イベント」という機能を提供しています。「特別な領域」に書かれた、

    procedure Button1Click(Sender: TObject);

というのは、このメッセージの内、Delphi のこのアプリケーション (プロジェクト) でこのボタンをクリックした時のメッセージだけを捕えることができます。

この文は、普通のメソッドと同じ書式です。
しかし、[オブジェクト・インスペクタ] で作成すると、このメソッドはイベントを処理する専用のメソッドとなり、「特別な領域」に自動的に書かれます。Delphi はこの「特別な領域」に書かれた内容に基づいて、実行時に特別な処理を行います

これを、例えば、ネット上にあるサンプルコードをコピーして「特別な領域」に、次のように貼り付けたとします。このコードは、既にテストしたユニットのコードと全く同じになりますが、Button1 をクリックしても、実際には何も起こりません。ダイアログは表示されません。



type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;


あるいは、


type
  TForm1 = class(TForm)
    Button1: TButton;
  private
    { Private 宣言 }
    procedure Button1Click(Sender: TObject);
  public
    { Public 宣言 }
  end;


のように、private 部 (プライベート部) に書いたり、


type
  TForm1 = class(TForm)
    Button1: TButton;
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    procedure Button1Click(Sender: TObject);
  end;


のように、publuc 部 (パブリック部) に書いてもイベントのメソッドとして解釈してくれません。
つまり、Button1 をクリックしても、これらのメソッドは自動では実行されません。

    Button1: TButton;

も同じで、設計時にボタンを配置したことにより「特別な領域」に書かれています。これは、実行時にTButton というクラスの Button1 というインスタンス (実体) を設計時の設定で自動的に作成することを意味しています。もし、private 部か public 部に書いた場合は、実行時に Create メソッドでそのインスタンス、つまり、実体を作成しなければなりません。

実はイベントはプロパティの一種です。VCL の、つまり Delphi のソースコードを見ると property のところに OnClick と書かれている箇所があります。Delphi は実行時にそのプロパティの定義があるかを調べているのです。

これは、他のプロパティの設定も同じです。[オブジェクト・インスペクタ] で定義されていなければ、つまり、プロパティが設定していなければ、そのイベントを処理できません。設定がなければ処理のしようがありません。
したがって、一般的なプロパティがコードで指定可能な様に、イベントも他のメソッドを割当てることができます。ただし、その引数の数と型と順序はイベントの種類によって決まっています。これらを変更することはできません。

イベントは任意の名前にすることができますが、Button1Click であれば、Button1 を Click (クリック) した時のイベントであることが分かります。

イベントは、必ず、オブジェクトインスペクタで作成します。そうしなければ、メッセージを処理するという意味のイベントとしては機能しません。単なるメソッドと同じになってしまいます。自動で実行してくれません。




07_イベント以外のメッセージ処理


メッセージは、コンポーネントに用意されたイベント以外でも処理できます。

そのためにはメッセージ処理のためのメソッドを作成して行います。これは、主にイベントだけでは処理できないメッセージを処理したい場合に使用します。
[コード補完機能等の利用] で説明している方法を用いると、これらのメソッドを比較的簡単に作成できます。また、コンポーネントパレットの [Additional] タブの TApplicationEvents を利用してもメッセージの処理を行うことができます。

Delphi では、メソッドに応じたメッセージが発生すると、自動的にこれらのメソッドを実行する仕組みになっています。Windows では、1 秒間に何千ものメッセージが飛び交っています。メッセージの処理には十分な注意が必要です。

他のアプリケーションが発生する、あるいは発生したメッセージは、基本的には捕らえることはできません。他のアプリケーションのメッセージの検出には、例えば、グローバルフックと呼ばれる仕組みを実装する方法があります。
システムが発生するメッセージは、内容にもよりますが、検出できるメッセージもあります。例えばシステムのシャットダウンのメッセージを検出することができます。

メッセージは、何かの操作をすると発生するわけですが、逆に、あるコントロール等に、メッセージを送信すると、目的の操作をさせることもできます。ただし、他のアプリケーションに対しての操作には制限があります。





08_特別な領域を dfm ファイルで確認


上で述べた、OnClick がプロパティの一種であることを dfm ファイルで確認することができます。次のリストでは、Button1 の OnClick イベントを [オブジェクト・インスペクタ] で Button1Click という (デフォルトの) 名前で設定し、比較のために private 部に

     Button5: TButton;
     procedure Button2Click(Sender: TObject);

という TButton の宣言とメソッドを、キーボードから入力して作成してみました。


リスト2
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    Button5: TButton;
    procedure Button2Click(Sender: TObject);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
//
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
//
end;

end.


「特別な領域」の内容は、今回の場合 Unit1.dfm というファイルに保存されます。それが下のリストです。このコードには、private 部にある

     Button5: TButton;
     procedure Button2Click(Sender: TObject);

に該当するコードはありません。もちろん、Button5 はフォームを表示しても見えません。
何故なら、Button5 は、コンポーネントパレットから配置したものではありませんし、Button2Click のコードは、オブジェクトインスペクタを使用して作成したものではないからです。したかって、特別な領域の情報としては保存されません。


リスト3
Unit1.dfm ファイルの内容
object Form1: TForm1
  Left = 241
  Top = 106
  Width = 300
  Height = 150
  HorzScrollBar.Visible = False
  VertScrollBar.Visible = False
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = SHIFTJIS_CHARSET
  Font.Color = clWindowText
  Font.Height = -12
  Font.Name = #65325#65331' '#65328#12468#12471#12483#12463
  Font.Style = []
  OldCreateOrder = False
  Position = poDesktopCenter
  Scaled = False
  PixelsPerInch = 96
  TextHeight = 12
  object Button1: TButton
    Left = 103
    Top = 47
    Width = 75
    Height = 25
    Caption = 'Button1'
    TabOrder = 0
    OnClick = Button1Click
  end
end




09_イベントの手続きは単なるメソッドとして使用できる


イベントの実装部の先頭は、procedure となっています。
Delphi には、この他に、先頭が function では始まるルーチンがあります。これは文字通り「関数」です。数学の関数は、次のように、引数の値を元に、ある計算、あるいは処理結果を返します。Delphi の関数も同じです。実行結果を返すのが関数、実行結果を返さないのは手続きです。
手続きでは、処理した結果を、引数で戻すようにすることもできます。


    結果受取変数名 = 関数名 (引数1, 引数2, ... )

イベントの処理も手続きも procedure で始まっています。したがって、イベントの手続きは、単なる手続き、つまり、メソッドとして使用できます。これを実際に確認してみます。

新規にプロジェクトを作成して、下図のようにコンポーネントを配置します。



図14
設計時の画面
  • 新規にプロジェクトを作成してコンポ類を配置

そして、次のコードのようなイベントを作成します。イベントはオブジェクトインスペクタで作成して、イベントハンドラとしてのコードを書きます。


リスト4
新規にプロジェクトを作成して、以下のイベントを作成する
//-----------------------------------------------------------------------------
//  Form1のOnCreateイベント処理
//-----------------------------------------------------------------------------
procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.Lines.Text := 'テスト文字列';
end;

//-----------------------------------------------------------------------------
//  Button1のOnClickイベント処理
//-----------------------------------------------------------------------------
procedure TForm1.Button1Click(Sender: TObject);
begin
  //Button2クリックのコード(Button2Click)を実行する
  //コードを実行するだけで、Button2をクリックするわけではない
  Button2Click(Memo1);
end;

//-----------------------------------------------------------------------------
//  Button2のOnClickイベント処理
//-----------------------------------------------------------------------------
procedure TForm1.Button2Click(Sender: TObject);
var
  LText : String;
begin
  LText := TMemo(Sender).Lines.Text;
  MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
end;


コンパイルして実行し、Button1 をクリックします。
上のコードでは、Button2Click の引数の Sender に Memo1 を渡しています。Memo1 の Lines.Text プロパティの値が取得されて表示されることが確認できます。
この時、Button2Click(Memo1) は単なるメソッドとして実行されます。Button2 のイベントとしては機能しません。Button2 のクリックイベントは発生しません。したがって、Button2 がクリックされることはありません。


図15
コンパイルして Button1 をクリック
  • Memo1 の Lines.Text の値が表示される

次に Button2 をクリックします。
Button2 をクリックすると、Sender はクリックしたオブジェクト自身で Button2 です。
Button2 は TButton クラスのインスタンス (実体) です、TButton には Lines というプロパティは存在しません。したがって下図のように、アドレスに対する読み取り違反のエラーが発生します。ないものは読み取ることができません。


図16
Button2 クリックはエラーとなる




10_引数の Sender: TObject とポインタと型キャスト


Delphi ではイベント処理の最初の引数は、慣習的に Sender: TObject となっています。
TObject は、全ての「クラス型のオブジェクト」の祖先のクラスです。
下図は TButton の例ですが、TObject というクラスを拡張して、TPersistent というクラスになり、最後に TButton というクラスのコンポーネントになっています。

Delphi ではクラス型には、慣習的に大文字の T を付けるようになっています。Sender という変数名は、おそらく「送り主」とか「送り側」程度の意味で付けられたものと思われます。変数名なので何でもいいのですが、Delphi では慣習的に Sender という名前が使われています。



前項の Button1Clickのコード以下のようになっています。
Button2Click の引数に Memo1 を渡しています。Button2Click の引数の型は TObject です。TObject 型の変数にクラス型の変数を渡すと、その変数のデータが格納されている (書き込まれている) メモリの先頭アドレスが渡されます。アドレスと言っても単なる整数値ではなく、ポインタという形式で格納されます。オブジェクトのデータがある場所を示すという意味でポインタと呼ばれています。
ポインタとは、何かを指し示すという意味です。つまり、Sender はポインタです。


procedure TForm1.Button1Click(Sender: TObject);
begin
  //Button2クリックのコード(Button2Click)を実行する
  //コードを実行するだけで、Button2をクリックするわけではない
  Button2Click(Memo1);
end;


データを受け取る側の Button2Click のコードは次のようになってまいす。
Sender にはデータが格納されているアドレスの情報しかありません。そこで TMemo で型キャストします。TMemo(Sender) というコードが、TMemo でキャストすることを意味しています。そうすると、そのアドレスに書き込まれているデータを TMemo のデータとして取得することができます。

イベントの手続きの引数が TObject になっていると、型キャストすることで、いろいな型のオブジェクトが扱えます。
クラス型のオブジェクトの型を確認するには、次のように、クラス演算子 is を使用した処理方法が使われています。


リスト5 
procedure TForm1.Button2Click(Sender: TObject);
var
  LText : String;
begin
  //引数のSenderの型を調べる
  if Sender is TMemo then begin
    LText := TMemo(Sender).Lines.Text;
    MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
  end else
  if Sender is TButton then begin
    LText := TButton(Sender).Caption;
    MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
  end;
end;


また、次のように、クラス型の変数名 (オブジェクトのインスタンス名) で条件分岐する方法もよく使用されています。


リスト6 
procedure TForm1.Button2Click(Sender: TObject);
var
  LText : String;
begin
  //引数のSenderのオブジェクトのインスタンス(実体名)を判定
  if Sender = Button2 then begin
    LText := Form1.Caption;
    MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
  end;
end;


上のコードで Button2 をクリックすると、例えば下図のようになります。


図17
上のコードの実行結果





11_Sender は何者か - イベントのコードを実行した犯人を調べる


TMemo も TButton も、コンポーネントは TComponent から継承してきた派生クラスです。
したがって、ほとんど場合 Sender を TComponent でキャストすることができます。すると、Sender のクラス名と名前を次のようなコードで知ることができます。
このコードは、上で使用したプロジェクトを使用して、コードだけ変更しています。

この方法は、クラス名によって処理を分岐したい場合に利用できます。



リスト7
Sender のクラス名と名前を知るためのコード
procedure TForm1.Button1Click(Sender: TObject);
begin
  //Button2クリックのコードを実行する
  //コードを実行するだけで、Button2をクリックするわけではない
  Button2Click(Sender);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  LText : String;
begin
  LText := Sender.ClassName + sLineBreak + TComponent(Sender).Name;
  MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
end;


下図が実際の実行結果です。


図18
Button1 をクリックした結果

図19
Button2 をクリックした結果




12_イベントの手続きを複数のコンポーネントで共有する


新規にプロジェクトを作成して、TButton を 2 つフォームに配置します。
そして、Button1 をダブルクリックして OnClick イベントを作成します。
これまでの説明では、イベントはオブジェクトインスペクタで作成すると説明しましたが、OnClick イベントは、コンポーネントをダブルクリックして作成することもできます。ただし、この方法は、全てのコンポーネントで OnClick イベントが生成されるわけではありません。


図20
設計時の画面
  • Button1 をダブルクリックする
  • Button1 の OnClick イベントが生成される

次に、Button2 を選択します。
Button2 のオブジェクトインスペクタの OnClick のリストから Button1Click を選択します。


図21
Button2 のオブジェクトインスペクタ
  • リストから Button1Click を選択する

Button1 のイベント処理を以下のコードのようにしたら、コンパイルして実行します。


リスト8
テスト用のユニットの全コード
Button1Click は Button2 の OnClick イベントでもある
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  LText : String;
begin
  if Sender = Button1 then begin
    LText := Button1.Caption;
  end else
  if Sender = Button2 then begin
    LText := Button2.Caption;
  end;
  MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
end;

end.


下図が実行した結果です。クリックしたボタンの Caption の値が表示されています。
このように、イベントは共有して利用できます。上のサンプルで説明した方法で、どのボタンをクリックしたかも検出できます。ただし、イベントの引数の数と名前と型の順番が一致しているイベントでないと共有できません。


図22
Button1 をクリックした結果
図23
Button2 をクリックした結果




13_メッセージでボタンクリックを検出する


最初にイベントを作成した時、イベントはウィンドウズのメッセージ処理、と書きました。
下のコードは、Button1 のクリックをメッセージで検出する例です。
もし イベントハンドラの作成という機能がない場合、このようなコードでボタンのクリックを検出することになります。事実、ネット上の C 言語または C++ 言語で書かれたサンプルでは、このような処理コードを見かけることがあります。
このコードは、
  • (1) メッセージの種類が WM_COMMAND である
  • (2) そのメッセージの通知コード が BN_CLICKED である
  • (3) メッセージの発生元が Button1 である
という条件の時に、Butoon1 のクリックと判定するものです。WndProc は、アプリで発生するメッセージ等の検出や処理に使用するメソッドです。


下の WndProc メソッドの宣言部 (interface 部) と実装部 (implementation 部) は次のようにして作成できます。
まず、public 部でキャレットを入力位置に移動します。[Ctlr] + [Space] を押します。コードの候補リストが表示されますが、そのまま Wn とタイプすると下図のように候補が絞られます。WndProc を選択して [Enter] キーを押すかクリックして、[Shift] + [Ctrl] + [C] で作成できます。
この時、WndProc の宣言部は自動的に protected 部に移動します。protected 部がない場合は作成されます。


図24
WndProc メソッドの作成方法

リスト9
メッセージでボタンクリックを検出する
イベントという機能がない場合は、このようにしてコントロールの操作を検出する
Windows の基本的な仕組みを使用するのではあるが、非常に面倒である
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
  protected
    procedure WndProc(var Message: TMessage); override;
  private
    { Private 宣言 }
  public
    { Public 宣言 }

  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  ボタンクリックをメッセージで検出する
//=============================================================================
procedure TForm1.WndProc(var Message: TMessage);
var
  LText : String;
begin
  if Message.Msg = WM_COMMAND then begin
    if TWMCommand(Message).NotifyCode = BN_CLICKED then begin
      if TWMCommand(Message).Ctl = Button1.Handle then begin
        //ここにButton1クリックの時の処理を書く
        //Button1のイベントのコードと同じ処理が可能
        LText := Button1.Caption;
        MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
      end;
    end;
  end;

  //元のWndPorcのメッセージ処理を行う
  //これがないと正常に動作しない
  inherited WndProc(Message);
end;

end.


上のコードに Button1.Handle というのがあります。これは Button1 というコントロールのウィンドウハンドルの値を意味しています。
ウィンドウと言うと、フォームのような「窓」を思い浮かべますが、ビジュアルなコンポーネントの多くもまたウィンドウです。ウィンドウはウィンドウハンドルを持つコントロールです。上のようなメッセージ処理や Windows API の関数を使用する場合は、コンポーネント名ではなくウィンドウハンドルを使用して操作します。

上の方の記事で、下図のようなクラスの継承図を示しました。Delphi では TWinControl から派生したコンポーネントがウィンドウハンドルを持つコントロールです。TImage 等、TGraphicControl から派生したコンポーネントにはウィンドウハンドルがありません。



Delphi の TButton, TListBox, TComboBox, TListView 等は、プロパティやメソッド、あるいはイベントを実装して、Windows が提供しているコントロールを使いやすくしたものです。
このことを「TButton は、Windows のボタンコントロールのラッパークラスである」という言い方をすることがあります。



図25
上のコードで Button1 をクリックした結果

メッセージを使用すると、コントロール (コンポーネント) のいろいろな操作を検出したり、操作することができます。
メッセージの検出や操作には、上のコードで使用した WndProc メソッドの他、サブクラス化という方法や、特定のメッセージの処理を追加した継承クラスを作成する方法もあります。

どんなコントロール (コンポーネント) が、どんなメッセージと関係があるかは、Windows API の説明で確認することになります。興味があれば調べてみてください。この記事の左側に、Windows API のオンラインヘルプに関係した記事への目次があります。





14_コードでボタンクリックのイベントを発生させる


最後にコードでボタンをクリックする、つまり、イベントを発生させるテストをしてみます。新規にプロジェクトを作成して、TButton を 2 つフォームに配置します。
そして、各々のボタンクリックのイベントのコードを下のコードのようにします。


リスト10
コードでボタンクリックのイベントを発生させる
procedure TForm1.Button1Click(Sender: TObject);
begin
  SendMessage(Button2.Handle, BM_CLICK, 0, 0);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  LText : String;
begin
  LText := Sender.ClassName + sLineBreak + TComponent(Sender).Name;
  MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
end;


上のコードの中の次の部分が Button2 をクリックするコードです。
前項で述べたように、Button2 のウィンドウハンドルの値に対して BM_CLICK というメッセージを送ります。3, 4 番目の値は、使用するメッセージによって異なります。BM_CLICK ではこれらの値は使用しません。共に 0 にしておきます。MSDN オンラインの記事の wParam が 3 番目の引数の値、lParam が 4 番目の引数の値を意味します。


  SendMessage(Button2.Handle, BM_CLICK, 0, 0);


前に、イベントを単なるメソッドとして実行するテストをしました。その時は、Button2 にフォーカスは移動しませんでした。このテストでは、実際に Button2 がクリックされるので、Button2 にフォーカスが移動して、Sender の値も Button2 であることが確認できます。



図26
上のコードで Button1 をクリックした結果
  • Button2 をクリックするコードを実行したので、Sender は Button2
  • Button2 をクリックしたので、Button2 がアクティブになっている

TSpeedButton 等、TGraphicControl から派生したコンポーネント (コントロール) はウィンドウハンドルを持ちません。したがって、上のコードのようにウィンドウハンドルに対してメッセージを送信することはできません。

Delphi には、コントロールにメッセージを送信するための Perform というメソッドが用意されています。この Perform メソッドは TGraphicControl でも TWinControl でも使用できます。
TGraphicControl は Windows のコントロールをラッピングしたコントロールではありません。Delphi 独自のコントロールです。Windows のコントロール関係のメッセージは使用できません。主に汎用の WM_XXX というメッセージを使用します。



次のコードは、TSpeedButton の OnClick イベントをコードで実行するサンプルです。
通常クリックは、マウスの左ボタンを押して放すという操作です。そこで、このコードではその通りのメッセージを送信しています。WM_LBUTTONDOWN はマウスの左 (L) ボタンを押す、という意味です。その次の行の WM_LBUTTONUP はマウスの左 (L) ボタンを放す (UP) するという意味です。つまり、押して放せばクリックということになります。
下の図が実行結果です。



リスト11
Perform メソッドを使用して、 コントロールへメッセージを送信する
//-----------------------------------------------------------------------------
//  Button1のOnClickイベント
//  オブジェクトインスペクタまたはButtto1のダブルクリックで作成する
//-----------------------------------------------------------------------------
procedure TForm1.Button1Click(Sender: TObject);
begin
  SpeedButton1.Perform(WM_LBUTTONDOWN, 0, 0);
  SpeedButton1.Perform(WM_LBUTTONUP, 0, 0);
end;

//-----------------------------------------------------------------------------
//  SpeedButton1のOnClickイベント
//  オブジェクトインスペクタまたはSpeedButton1のダブルクリックで作成する
//-----------------------------------------------------------------------------
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
  LText : String;
begin
  LText := Sender.ClassName + sLineBreak + TComponent(Sender).Name;
  MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
end;


図27
上のコードで Button1 をクリックした結果
  • 左側の下の小さい四角が SpeedButton1
  • SpeedButton1 をクリックするコードを実行したので、Sender は SpeedButton1

[備考]
上のコードを実行しても TSpeedButton にはフォーカスは移動しません。TSpeedButton はフォーカスを持たないコントロールだからです。TGraphicControl の派生コントロールはフォーカスを持ちません。このことを利用して、ボタンをクリックしても、入力フォーカスを移動させたくない場合に使用されています。