戻る ホーム 上へ 進む

3.4 マウスメッセージの処理

マウスメッセージに関しては Borland のドキュメントはかなり良く出来ています。実際、Delphi でプログラミングする際に、マウスメッセージの処理に関してソースコードを参照することはほとんどありません。しかし、マウス関連のイベントの正確な発生順序やイベントの有無が時には必要になる時があります。また、VCL は VCL 独自の Drag & Drop を提供しており、この仕組みがどうなっておるのかも気になるところです。本節では、VCL のマウスメッセージの処理について解説します。

3.4.1 VCL がマウスメッセージに対して行っていること

VCL は単純にマウスメッセージをコントロールに伝えるだけではなく、幾つかの前処理を行ってマウスメッセージの処理を楽にしてくれます。VCL の提供するキーボードに関する機能は以下の通りです。

  1. 標準マウスイベントの提供。
  2. 自動 Capture
  3. ポップアップメニューの表示
  4. Drag & Drop

3.4.1.1 標準マウスイベントの提供

VCL はマウスメッセージを翻訳してコントロールのマウスイベントに変換します。OnClick と OnDblClick は単純でわかりやすいハイレベルなマウスイベントで、一方、MouseDown, MouseUp, MouseMove はより詳細な情報が入手できる低レベルなマウスイベントです。

VCL のマウスメッセージ処理のの特筆すべき点は、グラフィックコントロールにもマウスイベントが起きることです。マウスメッセージは本来ウィンドウが受信するもので、グラフィックコントロールはウィンドウの上に描かれた絵に過ぎませんが、VCL はまるでそれがウィンドウで有るかのごとく見せかけてくれます。このため、マウスメッセージに関しては、VCL の利用者は、コントロールがウィンドウコントロールか、グラフィックコントロールかを意識する必要はありません。

3.4.1.2 自動 Capture

Windows のプログラミングでは通常マウスのキャプチャの処理はアプリケーションで明示的に書かねばなりませんが、VCL はこれを自動化しています。VCL は コントロールの ControlStyle Property が csMouseCapture を含んでいる場合、マウスを自動的に Caputure します。グラフィックコントロールさえマウスをキャプチャすることが出来ます

3.4.1.3 ポップアップメニューの表示

Delphi で作成したアプリケーションでは、コントロールに AutoPopup Property が True に設定されているポップアップメニューが関連付けられていると、右クリックでポップアップメニューが表示されます。ポップアップメニューの細かなコントロールは VCL が行ってくれるため本来必要な複雑な処理が必要ありません。

3.4.1.4 Drag & Drop

Delphi で作成したアプリケーションは Delphi 特有の Drag & Drop をサポートします。残念ながら Windows の Drag & Drop とは互換性はありませんのでアプリケーション間の Drag & Drop には使えませんが、アプリケーション内の(例えばフォーム間の)Drag & Drop には便利です。

3.4.2 マウスメッセージとイベントの発生順序

VCL のマウスメッセージ処理を理解する上で、マウスメッセージがどのような順序で発生するかを理解しておく必要があります。以下のようになります。

マウスボタンの押し方マウスメッセージマウスイベント備考
左ボタンをシングルクリック WM_LBUTTONDOWNOnMouseDownWM_LBUTTONDOWN に単純に対応
WM_LBUTTONUPOnClickOnClick イベントが発生する条件は下記参照
OnMouseUpWM_LBUTTONUP に単純に対応
左ボタンをダブルクリック WM_LBUTTONDOWNOnMouseDownWM_LBUTTONUP に単純に対応
WM_LBUTTONUPOnClickOnClick イベントが発生する条件は下記参照
OnMouseUpWM_LBUTTONUP に単純に対応
WM_LBUTTONDBLCLKOnDblClickWM_LBUTTONDBLCLK に単純に対応
OnMouseDownWM_LBUTTONDBLCLK に単純に対応
WM_LBUTTONUPOnMouseUpWM_LBUTTONUP に単純に対応
右ボタンをシングルクリック WM_RBUTTONDOWNOnMouseDownWM_RBUTTONDOWN に単純に対応
WM_RBUTTONUPOnMouseUpWM_LBUTTONUP に単純に対応
ポップアップメニューの表示表示の条件は下記参照
右ボタンをダブルクリック WM_RBUTTONDOWNOnMouseDownWM_RBUTTONDOWN に単純に対応
WM_RBUTTONUPOnMouseUpWM_RBUTTONUP に単純に対応。この後ポップアップメニューが表示される場合は、メニューにマウスメッセージが奪われるのでダブルクリックにならない。
WM_RBUTTONDBLCLKOnMouseDownWM_RBUTTONDBLCLK に単純に対応
WM_RBUTTONUPOnMouseUpWM_RBUTTONUP に単純に対応
中ボタンをシングルクリック WM_MBUTTONDOWNOnMouseDownWM_MBUTTONDOWN に単純に対応
WM_MBUTTONUPOnMouseUpWM_MBUTTONUP に単純に対応
中ボタンをダブルクリック WM_MBUTTONDOWNOnMouseDownWM_MBUTTONDOWN に単純に対応
WM_MBUTTONUPOnMouseUpWM_MBUTTONUP に単純に対応。
WM_MBUTTONDBLCLKOnMouseDownWM_MBUTTONDBLCLK に単純に対応
WM_MBUTTONUPOnMouseUpWM_MBUTTONUP に単純に対応
マウスを動かすWM_MOUSEMOVEOnMouseMoveWM_MOUSEMOVE に単純に対応

表内のイベントの順番は、実際に起こるイベントの順を正確に表しています。表から判るように、マウスのボタンによって発生するイベントが微妙に違います。OnClick/OnDblClick は左ボタンでしか発生しません。また、右ボタンはポップアップメニューの表示を行います。後で説明しますが、Drag & Drop は左ボタンの担当です。

上記のメッセージは同一のコントロールに来るとは限りません。コントロールの ControlStyle Property に csCaptureMouse が含まれていない場合は マウスメッセージのキャプチャが行われないので、どのコントロールにメッセージが届くかはマウスカーソルの位置によって決まります。逆に csCaptureMouse が含まれている場合は、WM_LBOTTONDOWN/WM_LBUTONDBLCLK がコントロールに届くとマウスがキャプチャされ、WM_LBUTTONUP でキャプチャが解除されます。自動キャプチャが左ボタンにしか働かないことに注意してください。

OnClick イベントは他のマウスイベントと比べ発生条件がきついのが特徴です。OnClick イベントは、あるコントロールが WM_LBUTTONDOWN メッセージを受け、その後に同じコントロールが自分のクライアント領域内で WM_LBUTTONUP を受け取った場合で、かつコントロールの Control Style Property に csClickEvent が含まれている場合にのみ発生します。WM_LBUTTONUP メッセージだけを受け取っても発生しません。また WM_LBUTTONDOWN を受け、その後 WM_LBUTTONUP を受け取っても、その座標がコントロールのクライアント領域から外れていれば発生しないのです。例えば、ボタンコントロールをマウスで押し、そのままドラッグしてボタンの外でボタンを離すと OnClick イベントは起こりません。しかし OnMouseUp は起こります。

右ボタンをクリックすると WM_RBUTTONUP のタイミングでポップアップメニューを表示しようとします。もちろんメニューが無ければ何も起きません。VCL は、WM_RBUTTONUP を受け取ったコントロールを起点に、コントロールに関連付けられた AutoPopup Property が True になっているポップアップメニューを親コントロールの方向遡っていってフォームに達するまでチェックします。もし、途中でポップアップメニューが見つかればそれを表示します。キーボードメッセージ処理のショートカットの探索とは違って、AutoPopup を考慮する点と、メインメニューはチェックしない点に注意してください。

3.4.3 マウスメッセージ処理の詳細

詳細といっても、マウスメッセージの処理内容はほとんど 3.4.2 で出尽くしているため、処理の内容を簡単に説明します。

マウスメッセージはウィンドウコントロールしか受け取れないため、メッセージはまず TWinControl.Wndproc メソッドで処理されます。ここでの処理は簡単です。WndProc はコントロール配列を調べてグラフィックコントロールの位置を調べ、マウスメッセージを受け取るべきグラフィックコントロールが有るか調べます。もし有れば、そのコントロールにメッセージを送ります。また既にマウスがキャプチャ済であれば、そのコントロールを調べてそこに送ります。グラフィックコントロールがキャプチャを行う場合、実際には Window Control がキャプチャを肩代わりし、Window Control がグラフィックコントロールにメッセージを流し込みます。

TWinControl.WndProc の処理が終わると、TControl.WndProc が処理を引き継ぎます。TControl.WndProc はマウスメッセージが来ると、まず、ControlStyle Property に csDoubleClick が含まれているかを調べます。無ければ WM_LBUTTONDBLCLK/WM_MBUTTONDBLCLK/WM_RBUTTONDBLCLK メッセージを WM_LBUTTONDOWN/WM_MBUTTONDOWN/WM_RBUTTONDOWN に変換します。その後、メッセージが WM_LBUTTONDOWN/WM_LBUTTONDBLCLK ならば、DragMode property をチェックして,Drag & Drop(後述)を始めるべきか調べ、始めないのならば ControlState Property に左ボタンが押されたこと(csLButtonDown)を記録します。WM_LBOTTONUP ならば CpntrolState Property から csLButtonDown を削除します。csLButtonDown は OnClick イベントを起こすかどうか判断する時に使われます。これらの処理後、メッセージは diaptch メソッドに渡され、メッセージ処理メソッドで処理されます(下図参照)。

Figure 3.4-1

図3.4-1 マウスメッセージ処理の流れ

マウスメッセージの処理とキーボードメッセージの処理の最大の違いはイベントの発生するタイミングです。キーボード関連のイベント(OnKeyDown, OnKeuUp 等)はコントロールのキーボードメッセージ処理が始まる「前」に呼び出されましたが、マウス関連のイベント(OnMouseDown, Click 等)はコントロールのマウスメッセージ処理の最後に起こります。上図から判りますように、コントロールの DefaultHandler の処理が終わってからイベントが起きることに注意してください。例えば、エディットコントロールをマウスで左クリックすると、カーソルが I-Beam に変わり、I-Beam が文字間の正しい位置に移動した「後に」 OnMouseDown イベントや Click イベントが起こります(上図参照)。

TControl で定義されている各メッセージ処理メソッド(WMLButtonDown, WMLButtonUp....)は Inherited を呼び出した後、メッセージの種類に応じて必要な処理メソッドを呼び出します。下図にメッセージ処理メソッドとそれが呼び出す各種処理の関係を示します。

Figure 3.4-2

図3.4-2 メッセージ処理メソッドと各種処理の関係

WMLButtonDown/WMLButtonDblClk メソッドはマウスのキャプチャの処理を行います。ControlStyle Property に csCaptureMouse が含まれている場合はここでキャプチャが行われます。座標がグラフィックコントロールの中をさしている場合は、どのグラフィックコントロールにマウスメッセージを送るべきかを決めるため、CaptureControl Property にそのグラフィックコントロールが記録されます。また、WMLButtonDblClk メソッドは ControlStyle Property に csClickEvent が含めれていれば、DblClick メソッドを呼びだし、OnDblClick イベントを起こします。WMLButtonDown/WMLButtonDblClk メソッドは上記の処理後 MouseDown メソッドを呼び出し OnMouseDown イベントを起こします。

WMLButtonUp メソッドは必要ならばキャプチャを解除し、3.4.2 で述べた条件にしたがって、ControlStyle Property に csClickEvent が含めれていれば Click メソッドを呼び OnClick イベントを起こします。その後、DoMouseUp メソッドを呼び出し OnMouseUp イベントを起こします。

WMRButtonUp メソッドは DoMouseUp メソッドを呼び OnMouseUp イベントを起こした「後」、ポップアップメニューを探索し、有ればそれを表示します。ポップアップメニューの探索はまず自分から始め、親のコントロールを遡ってフォームまで行います。途中で AuoPopup Property が True の ポップアップメニューが見つかればそれが表示されます。

WMLButtonDown/WMLButtonDblClk/WMLButtonUp/WMRButtonUp 以外のメッセージ処理メソッドでは、Inherited を呼び出した後、図3.4-2 のようにメソッドを呼び出すだけです(WMMouseMove は例外。後述)。

図中の DoMouseDown/DoMouseUpの役割は、コントロールの Control Style Property を調べ、csNoStdEvent が含まれているかチェックすることです。但し、WMMouseMove だけは例外で csNoStdEvent のチェックは WMMouseMove で行っています。もし含まれていれば、OnMouseDown/OnMouseUp/OnMouseMove イベントは発生しません。

図中に示されたイベントディスパッチパッチャは単純にイベントハンドラを呼び出すだけのメソッドです。これらのメソッドは dynamic なので、継承先のコントロールでこれらのメソッドを override すれば、マウスメッセージの処理に介入出来ます。但し、前述のように、マウスメッセージのイベントディスパッチャはマウスメッセージ処理の最後に呼び出されるので、既存のマウスメッセージ処理の前に処理を挟みたい場合は、WndProc メソッドを Override するか、メッセージ処理メソッドを Override してください。

3.4.4 Drag & Drop

Drag & Drop 処理の面白いところは、Drag の開始以外ではコントロールのマウスメッセージ処理を全く使っていないところです。Drag & Drop 処理は TDragObject型のインスタンスがコントロールセンターとなりメッセージ処理を行います。以下にメッセージ処理のシーケンスを示します。

Figure 3.4-3

図3.4-3 Drag & Drop 処理

Drag & Drop は、通常 TControl.Wndproc が WM_LBUTTONDOWN を受信し、コントロールの DragMode が dmAutomatic の場合に開始されます。この条件が整った場合、TControl.WndProc は、BeginDrag メソッド呼び出します。BeginDrag メソッドは、もしマウスの左ボタンが押下中なら、コントロールに WM_LBUTTONUP を送って、マウスのキャプチャを解除し、その後 DragObject を作成します。BeginDrag を明示的に呼んで Drag & Drop を開始することも出来ます。しかし BeginDrag を呼ぶと マウスの左ボタンが押下状態でなくても Drag & Drop が始まってしうので注意が必要です。OnMouseDown イベントから BeginDrag を呼ぶのが良いでしょう。また BeginDrag は強制的に WM_LBUTTONUP を送るので、ボタンを押したままなのに OnMouseUp や OnClick イベントが発生します。注意してください。

DragObject が作成されると、Drag & Drop の主役はコントロールから DragObject に移ります。DragObject は TDragObject 型のインスタンスです。DragObject は見えないウィンドウを作成し、これにマウスをキャプチャします。ドラッグ中は全てのマウスメッセージが DragObject に届き、コントロールのマウスメッセージ処理は使われません。従って、コントロールを新たに作成する時、TControl.WndProc の Drag & Drop の検出を邪魔するような Override をしない限り、新しいコントロールを作成する際、Drag & Drop のことを気にする必要はありません。

Drag & Drop は DragObject に WM_MOUSEMOVE が受信され続ける限り続きます。 Drag & Drop の処理の本体は TDragObject.DragTo メソッドです。

DragTo メソッドは、CM_DRAG(wParam= dmFindTarget) メッセージを使って、現在マウスカーソルにあるコントロールを見つけます。これは2段階で行われます。DragTo メソッドは WindowFromPoint API でマウスポインタの所に有るウィンドウを見つけ、そこに CM_DRAG(wParam= dmFindTarget) メッセージを SendMessage API で送ります。もし WindowFromPoint API で ウィンドウが見つからなければ「ターゲット探し」は失敗します。このメッセージを受けた Window Control は、マウスカーソル位置に自分の子のグラフィックコントロールが有ればそのオブジェクト参照を返します。また無ければ自分のオブジェクト参照を返します。メッセージが送られたウィンドウが Delphi の Window Control で無ければ 0 が返り Drag & Drop の「ターゲット探し」は失敗します。このように DragObject と ターゲットの Window Control の共同作業で、ターゲットが絞り込まれるようになっています。

DragTo メソッドは ターゲットが確定すると、もしターゲットが変わった場合は、旧ターゲットに CM_DRAG(wParam=dmDragLeave) 、新ターゲットに CM_DRAG(wParam=dmDragEnter)を送ります。そして現在のターゲットに CM_DRAG(wParam=dmDragMove) を送ります。これらのメッセージは ターゲット側の CM_DRAG メッセージのメッセージ処理メソッドで処理され、OnDragOver イベントが発生します。OnDragOver イベントには Accept という参照渡しのパラメータがありますが、これを True に書き換えると ターゲットコントロールが Drag & Drop を受け入れることを表明したことになります。 Accept は SendMessage API の戻り値として、 DragTo メソッドに通知されます。但し、DragTo メソッドは これを CM_DRAG(wParam=dmDragMove)の時だけ見ています。DragTo メソッドは CM_DRAG(wParam=dmDragMove) の戻り値を見て、ドラッグ中を示すカーソルを表示するか crNoDrop (ドロップ不能)を表示するかを決めます。Drop が可なら、カーソルは Drag & Drop 元の DragCursor property のものが使われます。

マウスのボタンが離され、 WM_LBUTTONUP が Drag Object に届くと DragDone procedure(メソッドではない)が呼び出され、これが Drag Object を破壊し、マウスのキャプチャも解除します。ターゲットコントロールには CM_DRAG(wParam=dmDragLeave) が送られ、これが Drag & Drop を受け入れるかどうかの最終判断に使われます。Accept = True だった場合は CM_DRAG(wParam=dmDragDrop) が、False だった場合は CM_DRAG(wParam=dmDragCancel) がターゲットコントロールに送られます。CM_DRAG(wParam=dmDragDrop)をターゲットが受信すると、OnDragDrop イベントが起こります。DragDone は最後に Drag & Drop 元のコントロールの DoEndDrag を呼び出します。DoEndDrag は Drag & Drop 元に OnEndDrag イベントを起こします。

以上で、Drag & Drop の説明は終わりです。繰り返しますが Drag & Drop 処理は TDragObject と CMDrag メッセージ処理メソッドで構成されており、コントロールのマウスメッセージ処理とは独立に実装されています。このためコントロールのマウスメッセージの処理が単純になっています。

最後に蛇足ですが、BeginDrag メソッドは DragObject を作成する前に、OnStartDrag イベントを起こします。参照渡しのパラメータ DragObejct は値が Nil の変数で、この変数に独自の TDragObject の継承クラスのインスタンスのオブジェクト参照を渡せば、Drag & Drop の挙動を変更することが出来ます。もし、DragObject を作成してセットした場合、Drag & Drop は それを DragObject として使います。この場合、OnEndDrag で DragObject を 明示的に Free しなければなりません。

OnStartDrag で DragObject を変更すれば、かなり自由に Drag & Drop の挙動を変更できるはずです。

3.4.5 まとめ

マウスメッセージ処理を以下にまとめます

  1. VCL ではキーボードメッセージはグラフィックコントロールにも届くように工夫されています。
  2. マウスメッセージは OnClick/OnDblClick/OnMouseDown/OnMouseUp/OnMouseMove イベントを起こします。キーボードメッセージとは異なり、これらのイベントはマウスメッセージ処理の最後に起こります。マウスメッセージに介入するために override 用のメソッドは Click/DblClick/MouseDown/MouseUp/MouseMove です。マウスメッセージ処理の、前に介入したい場合は、WndProc を Override するかメッセージ処理メソッドを Override します。
  3. VCL の Drag & Drop は Windows の Drag & Drop とは互換性の無い独自のもので、Drag & Drop はマウスメッセージ処理とは独立に実装されています。従って、コントロールを書く際、Drag & Drop の処理を気にせずにマウスメッセージの処理を書くことが出来ます。

まとめの蛇足

ここまでの説明で、コントロールの ControlStyle property がたくさん出てきたので、TControlStyle 型を簡単にまとめておきます。

 
フラグ意味
csAcceptControl設計時に子コントロールを貼り付けられる。
csCaptureMouseコントロールがマウスをキャプチャする。
csDesignInteractive設計時にマウスの右ボタンのメッセージが左ボタンのメッセージとしてコントロールに通知される。THeader でのみ使用。
csClickEventOnClick, OnDblClick Event が起きる。
csFramesFrame を描画する。
csSetCaptionCaption 又は Text Property が 明示的にセットされていない場合、Name Property の変更に連動する。
csOpaqueコントロールが背後を完全に隠すことを示す。
csDoubleClicksこの指定が無い場合は TWinControl.WndProc は WM_LBUTTONDBLCLK/WM_RBUTTONDBLCLK/WM_MBUTTONDBLCLK を WM_LBUTTONDOWN/WM_RBUTTONDOWN/WM_MBUTTONDOWN に変換する。
csFixedWidthChangeScale メソッドで幅が変化しない。
csFixedHeightChangeScale メソッドで高さが変化しない。
csNoDesignVisible設計時に表示されない。
csReplicatable不明。複製可能?
csNoStdEventsOnMouseDown/OnMouseMove/OnMouseUp/OnKeyDown/OnKeyUp/OnKeyPress イベントが起きない。
csDisplayDragImageドラッグカーソルの表示に常に ImageList を使う。詳細は未調査(^^;。

戻る ホーム 上へ 進む

inserted by FC2 system