GO BACK

Delphi教室 その7


「イベントを利用する」


■マウス関係のイベント


 今まで作成したサンプルは、全てボタンに設定して使うプロシージャでした。Buttonコンポーネントの「OnClick」というものに設定するわけですね。が、オブジェクトインスペクタの「イベント」タブには、この他にもたくさんの項目があります。ここでは、その他の主なイベントについて説明していきましょう。

 まずは、マウス関連のイベントからです。マウス関係のイベントには、いくつかの種類があります。OnClickもマウス関連のイベントですが、これとは別に、マウスボタンの操作やマウスの移動などに関するイベントがコンポーネントには用意されているのです。それらをざっと整理してみましょう。

procedure TForm1.○○MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);

procedure TForm1.○○MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);

procedure TForm1.○○MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);

 Delphiのイベントに割り付けるプロシージャ(イベントハンドラといいます)は、プロシージャ名を自由に付けられるので、ここではデフォルトで設定される名前のコンポーネント名部分を○○と仮にした形で書いてみました。ちょっとわかりにくいかも知れませんが、「要するにこういう形をしたプロシージャが割り付けられるんだな」ということがわかればいいでしょう。

 イベントに割り付けるプロシージャというのは、全て同じ形をしているわけではありません。そのもっとも重要な違いはパラメータです。OnClickでは「Sneder: TObject」というパラメータが1つついているだけでしたが、ここで上げた3つのプロシージャはもっとたくさんのパラメータが設定されていることがわかります。つまりこれは「イベントによっては、プロシージャを呼び出す際にいくつもの値を受け渡すようなものもある」ということなんですね。

 では、ここであげてあるプロシージャのパラメータについて、どういう役割を果たすものかまとめてみましょう。

Sender: TObject――イベントが発生したコンポーネントを示すもの。基本的に全てのイベントハンドラのプロシージャで第1パラメータとしてつけられる。(これについては後で改めて説明)

Button: TMouseButton――押したボタンを示すもの。TMouseButtonという独自のタイプの値で、以下のいずれかの値が設定される。
(mbLeft, mbRight, mbMiddle)

Shift: TShiftState――マウスボタンや機能キー(シフトやAltキーなど)の状態を示すもの。やっぱりTShiftStateという独自タイプの値で、以下の値の中で使ってるものを全てまとめた「集合型」の値になってる。
(ssShift, ssAlt, ssCtrl, ssLeft, ssRight, ssMiddle, ssDouble)

X, Y: Integer――イベントが起きた時のマウスポインタの横と縦の位置。これは、そのコンポーネントの左・上を基準とした値になる。

 とりあえず、最後のX, Yはわかりますね? Buttonは、どのボタンを使ったときにこのイベントが発生したかを示すもので、用意された値のどれかが入ります。

 問題は、Shiftですね。これは集合型の値なので、押したボタンや機能キー全てが値として設定されます。集合型、覚えてますか? TFontのStyleプロパティで出てきましたね。集合型のものは、inを使って「ある値が、この値の中に含まれているか」を調べることができました。ですから、例えば「Altキーが押されていたら何かする」というような場合なら、

if ssAlt in Shift then ・・・実行する命令・・・

こんな感じにすればいいわけです。

 では、簡単なサンプルを作ってみましょう。Imageコンポーネントを利用して、マウスでドラッグして図形を描くものを作ってみます。フォームデザイナにImageを1つ作成し、「MouseDown」「MouseMove」「MouseUp」のそれぞれのイベントにプロシージャを割り付けて下さい。

 


procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
	Image1.Canvas.Pen.Width := 0;
	Image1.Canvas.Brush.Color := clRed;
	Image1.Canvas.Ellipse(X - 10,Y - 10,X + 10,Y + 10);
	Image1.Canvas.PenPos := Point(X,Y);
end;

procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
	if ssLeft in Shift then
	begin
		Image1.Canvas.Pen.Color := clBlue;
		Image1.Canvas.Pen.Width := 5;
		Image1.Canvas.LineTo(X,Y);
	end;
end;

procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
	Image1.Canvas.Pen.Width := 0;
	Image1.Canvas.Brush.Color := clGreen;
	Image1.Canvas.Ellipse(X - 10,Y - 10,X + 10,Y + 10);
end;

 マウスでImage1をドラッグすると、開始地点に赤い円、終了地点に緑の円を描き、ドラッグにあわせて青い線を引いていきます。3つのプロシージャで行なっていることをよく見てみましょう。MouseDownで赤い円を描き、MouseMoveで青い線を、MouseUpで緑の円をそれぞえ描いているのがわかりますね?

 一つ注意すべきは、MouseMoveです。これは「マウスポインタが動いている間、続けて発生するイベント」なんです。ですから、ここに何かの処理を入れておくと、ドラッグ中、常にそれが呼び出し続けられることになります。重要なのは、「このイベントは、マウスボタンを押しているか否かに関係なく発生している」ということです。従って、マウスボタンを押している間だけ何かをしたければ、まず「if ○○ in Shift then ・・」という具合にして、特定のマウスボタンが押されている場合のみ処理を実行するようにしておかなければいけません。これは、よく忘れてしまいますので注意しておきましょう。

 

■Senderとオブジェクトのキャスト


 マウス関係でも出てきましたが、イベントハンドラでは、必ず第1パラメータに「Sender: TObject」というものが付けられます。これは、どういう役割を果たすものなんでしょうか。

 これは、イベントが発生したコンポーネントを示すものが送られてくるのだ、と先に触れました。だけど、良く考えてみると、このプロシージャ自身は「一体、どんなコンポーネントで利用されているか」はわからないわけです。つまり、コンポーネントが送られてくるといっても、それがButtonだかEditだかImageだか、全くわからないわけですね。

 ですから、どんなものであったとしても、うまいこと受け取れるような方法を考えないといけません。例えば、パラメータが「Sender: TButton」となっていたら、Buttonしか受け取れなくなってしまいます(TButtonというのはButtonコンポーネントの「クラス」というものです)。そこで、こうしたDelphiのコンポーネントの共通基盤のようになっているものを利用することにしたのです。それがTObjectというものです。

 Delphiのコンポーネントは、「クラス」と呼ばれるものとして作られています。クラスとは、独立して扱える部品の設計図に相当するものと考えて下さい。例えばButtonでは「TButton」というクラスがあって、その設計図を元にオブジェクトが作られ利用されるようになっているわけですね。これらコンポーネントのクラスは、全てこのTObjectというものをベースにして作られています。

 オブジェクト指向というものでは、「継承」という概念があります。これは、あるクラスの機能を全て受け継いで新しいクラスを作ることです。つまり、そうすることで、既にあるクラスを利用して効率的に新しいものが作れるようにしてあるわけですね。

 まぁ、オブジェクト指向や継承については一口に説明するのは難しいので「そういうものがある」とだけ知っていれば今は十分です。TObjectは、オブジェクトの基本的な機能だけをもったクラスなんですね。これを継承して、TButtonやTEditといったコンポーネントのクラスを作ってあるわけです。

 そこで、このSenderに話を戻します。あるクラスを継承して新しいクラスを作ると、そのクラスは、継承した元のクラスの性質を全て持っているのですね。それはつまり、継承元のクラスとしても扱えることを意味します。例えば、TObjectを継承してTButtonというButtonコンポーネントを作っているのですから、Buttonのオブジェクトは、全てTObjectのオブジェクトとしての性質も持っている、つまり「TObjectとして扱える」のです。

 そこで、イベントが発生したコンポーネントを、全て「TObjectとして」送るようにしたのが、Senderなのですね。

 Senderで送られてくるのはTObjectですが、これは「そう扱えるからTObjectとして送った」というだけで、本当はちゃんとしたコンポーネントのオブジェクトであるわけです。そこで、受け取ったら、本来のコンポーネントの形に戻してやれば、本来の形で利用できるようになります。

 前に、ある変数を別のタイプの値に変換することを「キャスト(型変換)」というといいましたが、これはオブジェクトでもできるんです。あるオブジェクトを、別のクラスのオブジェクトに変換するのも「キャスト」なんですよ。もちろん、全く関係ないものに変換したりはできません。ButtonをEditにしたりですね。けれど、この例のように「ButtonをTObjectにして渡したものを、再びTButtonに戻す」というような場合には、キャストが使えます。これは、以下のような形で記述します。

《オブジェクト》 as 《キャストする型》

《キャストする型》(《オブジェクト》)

 どちらの書き方でも同じです。例えば、SenderをTButtonにキャストするなら、「Sender as TButton」とか「TButton(Sender)」と書けばいいわけです。

 このSenderの使いかがわかると、いくつものコンポーネントで同じプロシージャをイベントハンドラに割り付けて共有するようなプロシージャが作れるようになります。

 簡単な実験をしてみましょう。フォームデザイナにButtonを3つほど作成します。そして、「OnClick」イベントの値に「ButtonClick」と入力してリターンして下さい。ButtonClickプロシージャがイベントハンドラとして割り付けられます。それを確認したら、他のボタンでもOnClickイベントに同じ名前のプロシージャを設定します。既にButtonClickプロシージャはできてますから、2つ目以降のボタンでは、OnClickの値部分にポップアップで「ButtonClick」が表示され、選べるようになっているはずです。

 全てのButtonのOnClickにButtonClickを設定したら、以下のようにプロシージャを記述し、実行してみましょう。クリックしたボタンのキャプションを画面に表示します。全て同じプロシージャを呼び出しているのに、ちゃんと押したボタンを認識して動いていることがわかるでしょう。


procedure TForm1.ButtonClick(Sender: TObject);
var
	btn: TButton;
begin
	btn := TButton(Sender);
	ShowMessage('押したのは、' + btn.Caption);
end;

 

 ここでは、「btn := TButton(Sender);」として、SenderをTButtonとして変数に代入し、そのCaptionを使って画面にメッセージを表示しています。Captionプロパティは、Editなどにはなく、Buttonにあるプロパティですから、btnがButtonコンポーネントとして使えていることがわかりますね。

 

■フォームの主なイベント


 さて、マウスなどはコンポーネントのイベントでしたが、イベントが用意されているのはコンポーネントだけではありません。フォームにもイベントは用意されています。――いや、フォームも実はコンポーネントの一種なんですが、フォームの場合には、いろいろと普通のコンポーネントではあまり使われないようなものが重要になってきたりするんですよ。

 例えば、「フォームができるときのイベント」。コンポーネントでもそういうものはあるのですが、フォームの場合、「画面にあらわれる前の下準備」みたいなことが必要になる例は多くあります。逆に、フォームを閉じる際のイベントなども必要となることはあるでしょう。また、「再描画のためのイベント」。Imageなどは、一度描画メソッドで描いたものはそのまま表示され続けますが、フォームの場合、リサイズしたりウィンドウを最小化したりといろいろ行なうと描画メソッドで描いた図形などは消えてしまうんです。そのために、画面を再描画する必要が生じた時に自動的に呼び出されるイベントなどをよく利用します。このあたりのイベントについて簡単に整理してみましょう。フォーム関係のイベントハンドラは、デフォルトでは全て「Form○○」というプロシージャ名で登録されますので、そのデフォルト名で整理しておきます。

procedure TForm1.FormCreate(Sender: TObject);
フォームが作成される際に発せられるイベント。起動時の初期化処理などに利用する。

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
閉じる要求がされた際に発せられるイベント。このイベントの段階ではフォームは閉じてない。この処理が終わった後で、フォームは閉じられる。もし、閉じるのをキャンセルしたい時には、CanCloseをFalseに変更すればいい。

procedure TForm1.FormResize(Sender: TObject);
フォームをリサイズした際に発せられるイベント。

procedure TForm1.FormPaint(Sender: TObject);
フォームを再描画する必要が生じた時に発生られるイベント。フォームに何かの描画をしたい時は、ここに記述すれば自動的に表示される。

 これらは、一番多用しそうなものをピックアップしたものと考えて下さい。フォーム関係のイベントは他にもまだまだあります。特に、フォームのクローズとリサイズは、実は2つのイベントが用意されていたりするんですが、まぁとりあえず上の4つだけ知っていれば、ちょっとしたフォームの処理はできるようになるはずですよ。

 いくつか補足しておきましょう。まず、FormCloseQueryですが、これはSenderの他に「var CanClose: Boolean」というパラメータが送られてきます。この最初のvarってのは何だ?と思った人もいることでしょう。これは「参照パラメータ」というものなんですが――要するに、このvarがついているものは、送られる元の変数そのものが渡されるのだ、と考えて下さい。普通、パラメータというのは値を変数に収めて渡されるわけで、渡されるのは元の変数とは別のものになるわけです。が、varは、元の変数がそのまま渡されるのです。ということは、この変数を書き換えると、大本の値が書き換わってしまう、ということです。

 このCanCloseは、このフォームを閉じていいかどうかを示す、コンポーネントの中の値であると考えるといいでしょう。これがTrueならば閉じ、Falseならば閉じる処理をキャンセルするようになっています。varで変数が渡されるため、この値を書き換えることで、フォームの閉じる処理をキャンセルできてしまう、というわけです。

 もう1つ、FormPaintについても触れておきます。通常、コンポーネントの表示というのは、画面の表示を更新する必要が生じた時、専用のイベントが発生し、それによって描画が行なわれるようになっています。この「再描画の必要が生じた時に発生するイベント」が、OnPaintイベントなのです。

 ただ、ほとんどのコンポーネントは、自身の中で再描画時に自動的に画面を更新する処理を持っていて、利用者が自分で再描画の処理をすることはほとんどありません。特にグラフィック関係のImageなどは、描いたイメージを記憶する仕組みを持っているため、一度描いてしまえば後は放っておいても大丈夫になっています。

 が、フォームはそうはいかないのです。例えば、ボタンをクリックするなどしてフォームに何か描いても(FormコンポーネントにもCanvasはあって描画できます)、ウィンドウをリサイズしたり他のウィンドウと重なったりすると、描いた部分は消えてしまうのです。つまり、常に「画面を更新する必要が生じたら描きなおす」というような処理にしておかないといけないわけです。そのためのものがFormPaintなのですね。

 ちょっとわかりにくいので、簡単な例を上げておきましょう。フォームデザイナでLabelを1つ作成し、フォームのOnPaintイベントに、以下のようなプロシージャを記述して下さい。これは、Labelコンポーネントの周りにちょっとした飾りをつけるプロシージャです。


procedure TForm1.FormPaint(Sender: TObject);
var
	x1,x2,y1,y2,i: Integer;
	c: TColor;

begin
	x1 := Label1.Left - 1;
	y1 := Label1.Top - 1;
	x2 := x1 + Label1.Width + 2;
	y2 := y1 + Label1.Height + 2;
	Form1.Canvas.Brush.Style := bsClear;
	For i := 0 to 10 do
	begin
		c := RGB(i * 25,0,0);
		Form1.Canvas.Pen.Color := c;
		Form1.Canvas.Rectangle(x1 - i * 2,y1 - i * 2,x2 + i * 2,y2 + i * 2);
	end;
end;

 

 上の図で、左がフォームデザイナの設計画面、右側が実行した際の表示です。ラベルの周りに飾りがついてるのがわかるでしょ? ウィンドウを最小化したりしても、これは消えたりせず、ちゃんと表示されます。同じ処理をButtonのOnClickなどから実行した場合にはどうなるか、比べてみるといいでしょう。

 他にもイベント関係はたくさんのものが用意されているんですが、とりあえずここであげたような基本なものだけ使えるようにしておきましょう。そうして、いくつもの種類のイベントを組み合わせて何かを作っていく、というアプローチに慣れるようにしましょう。

 


GO NEXT


GO HOME