SetCaptureとReleaseCaptureでマウスキャプチャを制御する
Windows APIでマウス操作を扱うとき、カーソルが対象ウィンドウの外へ移動した後もボタンの押下状態を追跡したい場合があります。通常のマウスメッセージはカーソル位置にあるウィンドウへ送られるため、ドラッグ操作や範囲選択では処理が途切れやすくなります。
この問題を解決する代表的な仕組みが、マウスキャプチャです。SetCaptureを呼び出すと、指定したウィンドウがマウス入力の送信先として優先され、カーソルがウィンドウ領域から出てもWM_MOUSEMOVEやWM_LBUTTONUPなどを受け取れるようになります。
キャプチャを使い終えたら、ReleaseCaptureで制御を解放します。解放のタイミングを誤ると、別のウィンドウが入力を受け取れなかったり、ドラッグ状態が残ったりするため、ボタンの押下から解放までの状態管理が重要です。
ここでは、Win32 APIにおけるマウスキャプチャの仕組み、基本的なコード、子ウィンドウとの関係、例外的な終了処理、関連APIとの使い分けを整理します。
マウスキャプチャの基本的な仕組み
通常、Windowsはカーソルの下にあるウィンドウを判断し、そのウィンドウへマウスメッセージを配信します。カーソルがボタンやクライアント領域の外へ出ると、移動先のウィンドウへメッセージの送信先が変わります。これは通常のクリック処理には適していますが、ドラッグ処理では不都合が生じます。
SetCapture(HWND hWnd)を実行すると、指定したウィンドウがマウス入力をキャプチャします。キャプチャ中は、カーソルが対象外へ移動しても、マウスの移動やボタン操作に関するメッセージをキャプチャ元のウィンドウで処理できます。
SetCapture(hwnd);
関数の戻り値は、直前にマウスをキャプチャしていたウィンドウのハンドルです。通常は戻り値を使わなくても動作しますが、別のウィンドウがすでにキャプチャ中でないかを確認したい場合には役立ちます。
SetCaptureを開始するタイミング
典型的な開始点は、WM_LBUTTONDOWN、WM_RBUTTONDOWN、またはWM_MBUTTONDOWNを受け取ったときです。たとえば、左ボタンを押した位置を保存してからキャプチャを開始すると、押下位置から現在位置までの矩形を計算できます。
case WM_LBUTTONDOWN:
dragging = true;
startPoint.x = GET_X_LPARAM(lParam);
startPoint.y = GET_Y_LPARAM(lParam);
SetCapture(hwnd);
return 0;
キャプチャ中のWM_MOUSEMOVEでは、lParamから得た座標がクライアント領域の外側になることがあります。負の値やクライアント領域の幅を超える値も想定し、必要に応じてGET_X_LPARAMとGET_Y_LPARAMで取得した座標を範囲内へ補正します。
ブラウザ上の操作感を検証する場合でも、ドラッグ、範囲指定、連続入力の考え方は共通します。たとえば、オンライン操作の事例を調べる際に、入力状態が途中で失われない設計を確認すると、デスクトップアプリケーションのUIテストにも応用できます。
ReleaseCaptureで入力を解放する
マウスキャプチャは、ボタンを離した時点で解放するのが基本です。WM_LBUTTONUPなどでドラッグ状態を解除し、ReleaseCaptureを呼び出します。
case WM_LBUTTONUP:
if (dragging) {
dragging = false;
ReleaseCapture();
InvalidateRect(hwnd, nullptr, TRUE);
}
return 0;
ReleaseCaptureの戻り値は成功すると0以外、失敗すると0です。ただし、現在のスレッドがマウスをキャプチャしていない場合などには解放できません。実装では、キャプチャを開始したかどうかを示すフラグを持たせておくと、不要な呼び出しを避けられます。
ウィンドウが破棄される、アプリケーションが非アクティブになる、別の操作へ切り替わるといった場合も、ドラッグ状態を残さない処理が必要です。WM_CANCELMODEを受け取ったときは、キャプチャを解放して内部状態を初期化する実装がよく使われます。
ウィンドウプロシージャでの状態管理
マウスキャプチャは、単にAPIを一度呼ぶだけでは完成しません。押下中か、ドラッグが開始されたか、現在の座標はいくつかといった状態を、ウィンドウごとに管理する必要があります。状態変数をウィンドウプロシージャのローカル変数に置くと、メッセージごとに消えてしまうため注意が必要です。
小規模な処理なら、ウィンドウプロシージャ外の変数やクラスのメンバー変数を使えます。複数のウィンドウを扱う場合は、GWLP_USERDATAに関連付けた構造体や、C++クラスのインスタンスへ状態を保持すると安全です。
case WM_CANCELMODE:
if (dragging) {
dragging = false;
ReleaseCapture();
}
return 0;
WM_CAPTURECHANGEDも確認しておくと、別のウィンドウへキャプチャが移ったことを検知できます。自分のウィンドウが意図せずキャプチャを失った場合は、マウスボタンがまだ押されているかに関係なく、ドラッグ状態を終了させる設計が適切です。
子ウィンドウとキャプチャの関係
親ウィンドウのクライアント領域に子ウィンドウが配置されている場合、どのウィンドウがSetCaptureを呼ぶかでメッセージの受け取り方が変わります。ボタンなどの子コントロールが自分でキャプチャを取得すると、親ウィンドウではなく、その子ウィンドウにマウスメッセージが届きます。
親ウィンドウでドラッグ全体を管理したい場合は、親側でキャプチャを開始する方法があります。ただし、子コントロールの標準動作と競合する可能性があるため、処理の責任範囲を明確にすることが大切です。
キャプチャは同じスレッドに属するウィンドウ間で扱われます。別スレッドのウィンドウへ無条件に入力を転送する仕組みではありません。複雑なUIでは、ウィンドウ階層とスレッド構成を確認してからキャプチャの所有者を決めます。
実装時に確認したいポイント
Windows APIの仕様を参照するときは、Windows技術情報のような関連資料も役立ちます。特にメッセージの発生条件、戻り値、スレッド単位の制約を確認すると、見た目では分かりにくい入力不具合を切り分けやすくなります。
実装前に、押下、移動、解放、キャンセルの各経路を状態遷移として整理しておくと、処理漏れを防げます。次の点を確認すると、基本的なドラッグ処理を安定させやすくなります。
- ボタン押下時に、開始座標とドラッグ状態を保存する
SetCaptureの呼び出し対象が意図したウィンドウか確認するWM_MOUSEMOVEでクライアント領域外の座標を処理する- ボタン解放時に
ReleaseCaptureを呼び、状態を初期化する WM_CANCELMODEやWM_CAPTURECHANGEDでも後始末を行う- ウィンドウ破棄時にキャプチャ状態が残らないようにする
関連APIとの使い分け
SetCaptureは、マウスボタンを押している間の連続操作を追跡するためのAPIです。カーソルを特定の位置へ移動させるSetCursorPosや、現在のカーソル位置を取得するGetCursorPosとは目的が異なります。また、キャプチャ中でもキーボード入力のフォーカスが自動的に移るわけではありません。
ドラッグ処理では、キャプチャの有無だけでなく、GetClientRectによる領域確認、ScreenToClientによる座標変換、InvalidateRectによる再描画を組み合わせることがあります。画面座標とクライアント座標を混同すると、カーソルが外へ出たときに描画位置がずれます。
| API・メッセージ | 主な役割 | 使用する場面 |
|---|---|---|
SetCapture |
マウス入力を指定ウィンドウへ集める | ドラッグ開始 |
ReleaseCapture |
マウスキャプチャを解放する | ボタン解放、キャンセル |
GetCapture |
現在のキャプチャ元を取得する | 所有状態の確認 |
WM_CAPTURECHANGED |
キャプチャ先の変更を通知する | 強制的な終了処理 |
WM_CANCELMODE |
モード変更時のキャンセルを通知する | 状態の後始末 |
SetCursorPos |
カーソルを画面座標で移動する | カーソル位置の制御 |
マウスキャプチャで覚えておきたいのは、SetCaptureを開始処理、ReleaseCaptureを終了処理として対にすることです。キャプチャ中の移動、ボタン解放、キャンセル、ウィンドウ破棄までを一つの状態遷移として設計すれば、カーソルが領域外へ出てもドラッグ操作を安定して追跡できます。