MFCの基礎 - マウス

提供: MochiuWiki : SUSE, EC, PCB

概要

MFCアプリケーションにおけるマウス処理は、Windowsのメッセージドリブン設計に基づいて実装される。

マウス入力は、WM_LBUTTONDOWN、WM_MOUSEMOVE、WM_MOUSEWHEELなどのWindowsメッセージとして処理される。
MFCは、これらのメッセージを処理するための便利なメッセージハンドラ (OnLButtonDown、OnMouseMove、OnMouseWheel等) を提供している。

  • マウスメッセージの種類と処理フロー
  • マウス位置の取得と座標変換
  • マウスボタン状態の取得
  • MFCのマウスイベントハンドラ
  • マウスカーソルの制御
  • マウスキャプチャによるドラッグ操作
  • マウストラッキングとホバー検出
  • マウス入力のシミュレーション


マウスメッセージのパラメータには、マウスの座標情報 (lParam) とボタン状態 (wParam) が含まれる。
座標はクライアント座標系で提供されるため、スクリーン座標との変換が必要な場合もある。

MFCのマウス処理を理解することにより、以下に示す機能を実装できる。

  • ドラッグアンドドロップ機能
  • 描画アプリケーションのペン操作
  • ズーム・パン操作
  • コンテキストメニューの表示
  • ツールチップやホバー効果の実装



マウスメッセージ

Windowsは、マウス入力を複数のメッセージとして処理する。

主要なマウスメッセージ
メッセージ 説明
WM_LBUTTONDOWN
WM_LBUTTONUP
左ボタンが押下された時と離された時に送信される。
WM_RBUTTONDOWN
WM_RBUTTONUP
右ボタンが押下された時と離された時に送信される。
WM_MBUTTONDOWN
WM_MBUTTONUP
中ボタンが押下された時と離された時に送信される。
WM_MOUSEMOVE マウスカーソルが移動した時に送信される。
WM_MOUSEWHEEL 垂直マウスホイールが回転した時に送信される。
WM_MOUSEHWHEEL 水平マウスホイールが回転した時に送信される。
WM_LBUTTONDBLCLK
WM_RBUTTONDBLCLK
ダブルクリックされた時に送信される。
WM_NCLBUTTONDOWN 等 非クライアント領域でのマウス入力時に送信される。


WM_LBUTTONDOWN / WM_LBUTTONUP

  • WM_LBUTTONDOWNメッセージ
    左ボタンが押下された時に送信される。
  • WM_LBUTTONUPメッセージ
    左ボタンが離された時に送信される。


※注意
WM_LBUTTONDOWNメッセージ と WM_LBUTTONUPメッセージ は、クライアント領域内でのマウス操作を検出する場合に使用する。

メッセージパラメータ
パラメータ 説明
wParam (nFlags) ボタンと修飾キーの状態
(MK_LBUTTON、MK_CONTROL、MK_SHIFT等)
lParam (point) マウスカーソルの位置 (クライアント座標)
下位ワード: X座標、上位ワード: Y座標


nFlagsのビットフラグ
フラグ 説明
MK_LBUTTON 左ボタンが押下されている
MK_RBUTTON 右ボタンが押下されている
MK_MBUTTON 中ボタンが押下されている
MK_CONTROL Ctrlキーが押下されている
MK_SHIFT Shiftキーが押下されている


WM_RBUTTONDOWN / WM_RBUTTONUP

  • WM_RBUTTONDOWNメッセージ
    右ボタンが押下された時に送信される。
  • WM_RBUTTONUPメッセージ
    右ボタンが離された時に送信される。


右クリックは、通常コンテキストメニューを表示するために使用される。
WM_RBUTTONUPメッセージのハンドラ内で、TrackPopupMenu 関数を呼び出してコンテキストメニューを表示する。

メッセージパラメータは、WM_LBUTTONDOWN / WM_LBUTTONUPと同様である。

WM_MBUTTONDOWN / WM_MBUTTONUP

  • WM_MBUTTONDOWNメッセージ
    中ボタンが押下された時に送信される。
  • WM_MBUTTONUPメッセージ
    中ボタンが離された時に送信される。


中ボタンは、スクロール操作や特殊な機能のトリガーとして使用されることが多い。
例えば、Webブラウザでは、中ボタンクリックで新しいタブでリンクを開く機能が実装されている。

メッセージパラメータは、WM_LBUTTONDOWN / WM_LBUTTONUPと同様である。

WM_MOUSEMOVE

WM_MOUSEMOVEメッセージは、マウスカーソルがクライアント領域内で移動した時に送信される。

このメッセージは、マウスカーソルの移動に伴って高頻度で送信されるため、処理内容は軽量に保つ必要がある。
重い処理を実装すると、アプリケーションの応答性が低下する。

WM_MOUSEMOVEメッセージは、以下の用途で使用される。

  • ドラッグ操作の実装
  • マウスカーソル追従型のツールチップ表示
  • 描画アプリケーションのペン操作
  • ホバー効果の実装


メッセージパラメータは、他のマウスメッセージと同様に、wParamにボタン状態、lParamに座標が含まれる。

WM_MOUSEWHEEL

WM_MOUSEWHEELメッセージは、垂直マウスホイールが回転した時に送信される。

メッセージパラメータ
パラメータ 説明
wParam 上位ワード : 回転量 (zDelta)
下位ワード : 修飾キーの状態
lParam マウスカーソルの位置 (スクリーン座標)


zDelta (回転量) は、WHEEL_DELTA (120) の倍数で表現される。

  • 正の値
    ユーザーから見て手前方向 (上方向) への回転
  • 負の値
    ユーザーから見て奥方向 (下方向) への回転


スクロール操作を実装する場合、zDeltaの正負を判定して、スクロール方向を決定する。

※注意
WM_MOUSEWHEEL メッセージの lParam は、スクリーン座標で提供されるため、クライアント座標への変換が必要である。

ダブルクリックメッセージ

  • WM_LBUTTONDBLCLKメッセージ
    左ボタンがダブルクリックされた時に送信される。
  • WM_RBUTTONDBLCLKメッセージ
    右ボタンがダブルクリックされた時に送信される。
  • WM_MBUTTONDBLCLKメッセージ
    中ボタンがダブルクリックされた時に送信される。


ダブルクリックメッセージを受信するには、ウィンドウクラスに CS_DBLCLKS スタイルを設定する必要がある。
MFCのビュークラスやダイアログクラスでは、このスタイルがデフォルトで設定されている。

ダブルクリックの時間間隔は、GetDoubleClickTime 関数で取得できる。
デフォルトでは500ミリ秒であり、SetDoubleClickTime 関数で変更できる。

メッセージパラメータは、通常のボタンメッセージと同様である。

非クライアント領域メッセージ

非クライアント領域 (タイトルバー、フレーム、スクロールバー等) でのマウス操作は、別のメッセージとして処理される。

  • WM_NCLBUTTONDOWN
    非クライアント領域で左ボタンが押下された時に送信される。
  • WM_NCRBUTTONDOWN
    非クライアント領域で右ボタンが押下された時に送信される。
  • WM_NCMOUSEMOVE
    非クライアント領域でマウスが移動した時に送信される。


これらのメッセージは、通常システムがデフォルト処理を行うため、カスタム処理が必要な場合のみハンドラを定義する。

メッセージの処理フロー

マウスメッセージの処理フローを以下に示す。

  1. ユーザがマウスを操作する。
  2. Windowsは、適切なマウスメッセージを生成する。
  3. メッセージは、カーソル位置のウィンドウに送信される。
  4. ウィンドウプロシージャは、メッセージハンドラを呼び出す。
  5. ハンドラ内で、マウス操作に応じた処理を実行する。


この処理フローを理解することにより、適切なメッセージハンドラを選択することができる。

クリック操作を処理する場合は、WM_LBUTTONUPメッセージを使用する。
ドラッグ操作を処理する場合は、WM_LBUTTONDOWN、WM_MOUSEMOVE、WM_LBUTTONUPの組み合わせを使用する。


マウス位置の取得

マウスの位置を取得するには、複数の方法がある。

マウス位置取得の方法
方法 説明
メッセージパラメータ (lParam) マウスメッセージ内で提供されるクライアント座標
GetCursorPos関数 現在のマウスカーソル位置をスクリーン座標で取得
GET_X_LPARAM / GET_Y_LPARAMマクロ lParamから座標を抽出


GetCursorPos関数

GetCursorPos 関数は、現在のマウスカーソル位置をスクリーン座標で取得する関数である。

関数のプロトタイプを以下に示す。

 BOOL GetCursorPos(LPPOINT lpPoint);


引数 (パラメータ) と 戻り値
種別 項目 説明
パラメータ lpPoint カーソル位置を受け取るPOINT構造体へのポインタ
戻り値 BOOL 成功した場合はTRUE、失敗した場合はFALSE


使用例を以下に示す。

 POINT pt;
 GetCursorPos(&pt);
 
 CString str;
 str.Format(_T("カーソル位置 (スクリーン座標): x=%d, y=%d"), pt.x, pt.y);
 AfxMessageBox(str);


GetCursorPos関数は、スクリーン座標を返すため、クライアント座標への変換が必要な場合が多い。

ScreenToClient関数 / ClientToScreen関数

座標系の変換には、ScreenToClient 関数 と ClientToScreen 関数を使用する。

関数のプロトタイプを以下に示す。

 BOOL ScreenToClient(HWND hWnd, LPPOINT lpPoint);
 BOOL ClientToScreen(HWND hWnd, LPPOINT lpPoint);


引数 (パラメータ)
項目 説明
hWnd 座標変換の基準となるウィンドウのハンドル
lpPoint 変換する座標を持つPOINT構造体へのポインタ


  • ScreenToClient関数
    スクリーン座標をクライアント座標に変換する。
  • ClientToScreen関数
    クライアント座標をスクリーン座標に変換する。


使用例を以下に示す。

 void CMyView::ShowCursorPosition()
 {
    // スクリーン座標でカーソル位置を取得
    POINT pt;
    GetCursorPos(&pt);
 
    // クライアント座標に変換
    ScreenToClient(&pt);
 
    CString str;
    str.Format(_T("カーソル位置 (クライアント座標): x=%d, y=%d"), pt.x, pt.y);
    AfxMessageBox(str);
 }


MFCでは、CWndクラスのメンバ関数として ScreenToClient メソッド と ClientToScreen メソッド が提供されている。

 POINT pt;
 GetCursorPos(&pt);
 ScreenToClient(&pt);  // CWndのメンバ関数


GET_X_LPARAMマクロ / GET_Y_LPARAMマクロ

lParamから座標を抽出するには、GET_X_LPARAM マクロ と GET_Y_LPARAM マクロを使用する。

これらのマクロは、windowsx.h ヘッダファイルで定義されている。

使用例を以下に示す。

 #include <windowsx.h>
 
 LRESULT CMyView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
 {
    if (message == WM_LBUTTONDOWN) {
       int xPos = GET_X_LPARAM(lParam);
       int yPos = GET_Y_LPARAM(lParam);
 
       CString str;
       str.Format(_T("クリック位置: x=%d, y=%d"), xPos, yPos);
       AfxMessageBox(str);
    }
 
    return CView::WindowProc(message, wParam, lParam);
 }


MFCのイベントハンドラでは、CPoint型のパラメータとして座標が提供されるため、これらのマクロは不要である。

 void CMyView::OnLButtonDown(UINT nFlags, CPoint point)
 {
    // pointは既にCPoint型で提供される
    CString str;
    str.Format(_T("クリック位置: x=%d, y=%d"), point.x, point.y);
    AfxMessageBox(str);
 
    CView::OnLButtonDown(nFlags, point);
 }



マウスボタン状態の取得

マウスボタンの状態を取得するには、複数の方法がある。

マウスボタン状態取得の方法
方法 説明
nFlags (wParam) マウスメッセージのパラメータから取得
GetAsyncKeyState関数 リアルタイムのボタン状態を取得


nFlagsからのボタン状態取得

マウスメッセージのwParamには、ボタンと修飾キーの状態が含まれる。

ビットフラグを確認することにより、複数のボタンが同時に押下されているかを判定できる。

使用例を以下に示す。

 void CMyView::OnMouseMove(UINT nFlags, CPoint point)
 {
    // 左ボタンが押下されている場合
    if (nFlags & MK_LBUTTON) {
       // ドラッグ中の描画処理
       DrawLine(point);
    }
 
    // 左ボタンと右ボタンが同時に押下されている場合
    if ((nFlags & MK_LBUTTON) && (nFlags & MK_RBUTTON)) {
       // 特殊な操作
       ZoomOperation(point);
    }
 
    // [Ctrl]キーを押下しながらドラッグしている場合
    if ((nFlags & MK_LBUTTON) && (nFlags & MK_CONTROL)) {
       // コピー操作
       CopyDrag(point);
    }
 
    CView::OnMouseMove(nFlags, point);
 }


nFlagsによるボタン状態の取得は、マウスメッセージハンドラ内で使用するのに適している。

GetAsyncKeyState関数

GetAsyncKeyState 関数を使用すると、リアルタイムのマウスボタン状態を取得できる。

関数のプロトタイプを以下に示す。

 SHORT GetAsyncKeyState(int vKey);


下表に、マウスボタン用の仮想キーコードを示す。

マウスボタンの仮想キーコード
定数 説明
VK_LBUTTON 左ボタン
VK_RBUTTON 右ボタン
VK_MBUTTON 中ボタン


使用例を以下に示す。

 void CMyView::OnTimer(UINT_PTR nIDEvent)
 {
    // タイマ内でマウスボタン状態を確認
    if (GetAsyncKeyState(VK_LBUTTON) & 0x8000) {
       // 左ボタンが押下されている場合
       m_isDragging = TRUE;
    }
    else {
       m_isDragging = FALSE;
    }
 
    // 中ボタンでスクロールモード
    if (GetAsyncKeyState(VK_MBUTTON) & 0x8000) {
       EnableScrollMode();
    }
 
    CView::OnTimer(nIDEvent);
 }


GetAsyncKeyState関数は、マウスメッセージの外でボタン状態を確認する場合に便利である。
例えば、タイマイベント内や、定期的なポーリング処理で使用する。


MFCでのマウスイベントハンドラ

MFCは、マウスメッセージを処理するための便利なメッセージハンドラを提供している。

主要なマウスイベントハンドラ
メソッド 説明
OnLButtonDown
OnLButtonUp
WM_LBUTTONDOWN / WM_LBUTTONUPメッセージの処理
OnRButtonDown
OnRButtonUp
WM_RBUTTONDOWN / WM_RBUTTONUPメッセージの処理
OnMButtonDown
OnMButtonUp
WM_MBUTTONDOWN / WM_MBUTTONUPメッセージの処理
OnMouseMove WM_MOUSEMOVEメッセージの処理
OnMouseWheel WM_MOUSEWHEELメッセージの処理
OnLButtonDblClk
OnRButtonDblClk
ダブルクリックメッセージの処理
OnSetCursor WM_SETCURSORメッセージの処理
PreTranslateMessage メッセージの前処理


OnLButtonDown / OnLButtonUp

OnLButtonDown イベントハンドラ と OnLButtonUp イベントハンドラ は、左ボタンの押下と解放を処理するハンドラである。

関数のプロトタイプを以下に示す。

 afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
 afx_msg void OnLButtonUp(UINT nFlags, CPoint point);


引数 (パラメータ)
項目 説明
nFlags ボタンと修飾キーの状態
(MK_CONTROL、MK_SHIFT等)
point マウスカーソルの位置 (クライアント座標)


OnLButtonDownハンドラ と OnLButtonUpハンドラを使用することにより、クリック操作やドラッグ操作を実装できる。

使用例を以下に示す。

 void CMyView::OnLButtonDown(UINT nFlags, CPoint point)
 {
    // ドラッグ開始
    m_bDragging = TRUE;
    m_ptStart = point;
 
    // マウスキャプチャを開始
    SetCapture();
 
    CView::OnLButtonDown(nFlags, point);
 }
 
 void CMyView::OnLButtonUp(UINT nFlags, CPoint point)
 {
    if (m_bDragging) {
       // ドラッグ終了
       m_bDragging = FALSE;
 
       // マウスキャプチャを解放
       ReleaseCapture();
 
       // ドラッグ完了処理
       CompleteDrag(m_ptStart, point);
    }
 
    CView::OnLButtonUp(nFlags, point);
 }


右ボタン (OnRButtonDown / OnRButtonUp) と 中ボタン (OnMButtonDown / OnMButtonUp) も同様のシグネチャを持つ。

OnMouseMove

OnMouseMove イベントハンドラは、マウス移動を処理するハンドラである。

関数のプロトタイプを以下に示す。

 afx_msg void OnMouseMove(UINT nFlags, CPoint point);


パラメータ
項目 説明
nFlags ボタンと修飾キーの状態
point マウスカーソルの位置 (クライアント座標)


OnMouseMoveハンドラは、ドラッグ操作や描画操作を実装する場合に使用する。

使用例を以下に示す。

 void CMyView::OnMouseMove(UINT nFlags, CPoint point)
 {
    // ドラッグ中の場合
    if (m_bDragging && (nFlags & MK_LBUTTON)) {
       // 線を描画
       CClientDC dc(this);
       dc.MoveTo(m_ptPrev);
       dc.LineTo(point);
 
       m_ptPrev = point;
    }
 
    // カーソル位置を表示
    CString str;
    str.Format(_T("X: %d, Y: %d"), point.x, point.y);
    GetParent()->SetWindowText(str);
 
    CView::OnMouseMove(nFlags, point);
 }


※注意
OnMouseMoveは高頻度で呼び出されるため、処理は軽量に保つ必要がある。
重い処理を実装すると、マウス追従が遅延し、ユーザ体験が低下する。

OnMouseWheel

OnMouseWheel イベントハンドラは、マウスホイール回転を処理するハンドラである。

関数のプロトタイプを以下に示す。

 afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);


引数 (パラメータ)
項目 説明
nFlags 修飾キーの状態
(MK_CONTROL、MK_SHIFT等)
zDelta ホイールの回転量
正の値: 手前方向 (上方向)、負の値: 奥方向 (下方向)
pt マウスカーソルの位置 (スクリーン座標)


zDeltaは、WHEEL_DELTA (120) の倍数で提供される。
通常のスクロールでは、1ノッチ (1クリック) で120の値が渡される。

使用例を以下に示す。

 BOOL CMyView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
 {
    // スクリーン座標をクライアント座標に変換
    ScreenToClient(&pt);
 
    // ホイールの回転量に応じてスクロール
    int scrollLines = zDelta / WHEEL_DELTA;
 
    if (zDelta > 0) {
       // 上にスクロール
       ScrollUp(scrollLines);
    }
    else {
       // 下にスクロール
       ScrollDown(abs(scrollLines));
    }
 
    // [Ctrl]キーを押下しながらホイール回転でズーム
    if (nFlags & MK_CONTROL) {
       if (zDelta > 0) {
          ZoomIn();
       }
       else {
          ZoomOut();
       }
    }
 
    return CView::OnMouseWheel(nFlags, zDelta, pt);
 }


OnSetCursor

OnSetCursor イベントハンドラは、マウスカーソルの形状を変更するハンドラである。

関数のプロトタイプを以下に示す。

 afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);


引数 (パラメータ)
項目 説明
pWnd カーソルを含むウィンドウへのポインタ
nHitTest ヒットテストコード (HTCLIENT等)
message マウスメッセージ


OnSetCursorハンドラは、マウスカーソルの形状をカスタマイズする場合に使用する。

使用例を以下に示す。

 BOOL CMyView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
 {
    // クライアント領域内の場合
    if (nHitTest == HTCLIENT) {
       // 編集モードの場合は十字カーソル
       if (m_bEditMode) {
          SetCursor(LoadCursor(NULL, IDC_CROSS));
          return TRUE;
       }
 
       // リサイズモードの場合はサイズ変更カーソル
       if (m_bResizeMode) {
          SetCursor(LoadCursor(NULL, IDC_SIZEALL));
          return TRUE;
       }
    }
 
    return CView::OnSetCursor(pWnd, nHitTest, message);
 }


OnSetCursorハンドラがTRUEを返す場合、カーソルの変更を処理済みとして扱う。
FALSEを返す場合、デフォルトのカーソル処理が実行される。

PreTranslateMessageメソッド

PreTranslateMessage メソッドは、メッセージがディスパッチされる前に呼び出されるメソッドである。
このメソッドをオーバーライドすることにより、メッセージの前処理やフィルタリングを実装できる。

関数のプロトタイプを以下に示す。

 virtual BOOL PreTranslateMessage(MSG* pMsg);


引数 (パラメータ) と 戻り値
種別 項目 説明
パラメータ pMsg メッセージ構造体へのポインタ
戻り値 TRUE メッセージを処理済み (DispatchMessageに渡さない)
FALSE メッセージを通常通り処理 (DispatchMessageに渡す)


使用例を以下に示す。

 BOOL CMyView::PreTranslateMessage(MSG* pMsg)
 {
    // 右クリックを無効化
    if (pMsg->message == WM_RBUTTONDOWN) {
       return TRUE;  // メッセージを消費
    }
 
    // マウスホイールの処理をカスタマイズ
    if (pMsg->message == WM_MOUSEWHEEL) {
       // カスタム処理
       CustomWheelHandler(pMsg);
       return TRUE;
    }
 
    return CView::PreTranslateMessage(pMsg);
 }


PreTranslateMessageメソッドは、マウス操作のデフォルト動作を変更する場合に有用である。


マウスカーソル

マウスカーソルの形状を制御することにより、ユーザに視覚的なフィードバックを提供できる。

カーソル制御の方法
方法 説明
SetCursor関数 カーソルを即座に変更
LoadCursor関数 システムカーソルまたはカスタムカーソルをロード
OnSetCursorハンドラ WM_SETCURSORメッセージで動的にカーソルを変更


システム定義カーソル

Windowsは、複数の標準カーソルを提供している。

主要なシステムカーソル
定数 説明
IDC_ARROW 標準の矢印カーソル
IDC_IBEAM テキスト選択用のIビームカーソル
IDC_WAIT 待機中を示す砂時計カーソル
IDC_CROSS 十字カーソル
IDC_HAND ハンドカーソル (リンクの上等)
IDC_SIZEALL 4方向サイズ変更カーソル
IDC_SIZENESW 右上/左下方向サイズ変更カーソル
IDC_SIZENS 垂直方向サイズ変更カーソル
IDC_SIZENWSE 左上/右下方向サイズ変更カーソル
IDC_SIZEWE 水平方向サイズ変更カーソル
IDC_NO 禁止カーソル
IDC_APPSTARTING 作業中カーソル (矢印+砂時計)


使用例を以下に示す。

 void CMyView::ChangeCursor(int cursorType)
 {
    HCURSOR hCursor = NULL;
 
    switch (cursorType) {
    case 0:
       hCursor = LoadCursor(NULL, IDC_ARROW);
       break;
    case 1:
       hCursor = LoadCursor(NULL, IDC_CROSS);
       break;
    case 2:
       hCursor = LoadCursor(NULL, IDC_HAND);
       break;
    case 3:
       hCursor = LoadCursor(NULL, IDC_WAIT);
       break;
    }
 
    if (hCursor != NULL) {
       SetCursor(hCursor);
    }
 }


カスタムカーソル

カスタムカーソルをリソースファイルから読み込む方法を以下に示す。

 // カスタムカーソルをロード (IDC_MYCURSORはリソースID)
 HCURSOR hCustomCursor = LoadCursor(AfxGetInstanceHandle(),
                                     MAKEINTRESOURCE(IDC_MYCURSOR));
 
 if (hCustomCursor != NULL) {
    SetCursor(hCustomCursor);
 }


カーソルファイル (.cur) から直接ロードする方法を以下に示す。

 HCURSOR hCursor = LoadCursorFromFile(_T("C:\\Cursors\\mycursor.cur"));
 
 if (hCursor != NULL) {
    SetCursor(hCursor);
 }


WM_SETCURSORメッセージ

WM_SETCURSORメッセージは、マウスカーソルの形状を変更する必要がある時に送信される。

OnSetCursorハンドラをオーバーライドすることにより、動的にカーソルを変更できる。

使用例を以下に示す。

 BOOL CMyView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
 {
    if (nHitTest == HTCLIENT) {
       CPoint pt;
       GetCursorPos(&pt);
       ScreenToClient(&pt);
 
       // 特定の領域の上にカーソルがある場合
       if (m_hotRect.PtInRect(pt)) {
          SetCursor(LoadCursor(NULL, IDC_HAND));
          return TRUE;
       }
    }
 
    return CView::OnSetCursor(pWnd, nHitTest, message);
 }


待機カーソルの表示

MFCは、待機カーソルを簡単に表示するためのメソッドを提供している。

  • BeginWaitCursor
    待機カーソル (砂時計) を表示する。
  • EndWaitCursor
    待機カーソルを元に戻す。


使用例を以下に示す。

 void CMyView::LongOperation()
 {
    BeginWaitCursor();  // 待機カーソル表示
 
    // 長時間かかる処理
    for (int i = 0; i < 10000; i++) {
       ProcessData(i);
    }
 
    EndWaitCursor();  // 元のカーソルに戻す
 }


CWaitCursorクラスを使用すると、スコープベースで自動的にカーソルを管理できる。

 void CMyView::LongOperation()
 {
    CWaitCursor wait;  // コンストラクタで待機カーソル表示
 
    // 長時間かかる処理
    ProcessData();
 
    // デストラクタで自動的に元のカーソルに戻る
 }



マウスキャプチャ

マウスキャプチャは、特定のウィンドウが全てのマウス入力を受け取る機能である。

キャプチャを開始すると、マウスカーソルがウィンドウの外に出ても、そのウィンドウがマウスメッセージを受信し続ける。
この機能は、ドラッグ操作の実装に不可欠である。

マウスキャプチャの関数
関数 説明
SetCapture マウスキャプチャを開始
ReleaseCapture マウスキャプチャを解放
GetCapture 現在キャプチャしているウィンドウを取得


SetCapture / ReleaseCapture / GetCapture

関数のプロトタイプを以下に示す。

 HWND SetCapture(HWND hWnd);
 BOOL ReleaseCapture(void);
 HWND GetCapture(void);


  • SetCapture
    マウスキャプチャを開始する。
    すべてのマウス入力が指定されたウィンドウに送信されるようになる。
  • ReleaseCapture
    マウスキャプチャを解放する。
  • GetCapture
    現在マウスキャプチャを行っているウィンドウのハンドルを返す。


MFCでは、CWndクラスのメンバ関数として提供されている。

 CWnd* SetCapture();
 static BOOL PASCAL ReleaseCapture();
 static CWnd* PASCAL GetCapture();


ドラッグ操作の実装例

マウスキャプチャを使用したドラッグ操作の実装例を以下に示す。

 class CMyView : public CView
 {
 protected:
    BOOL m_bDragging;    // ドラッグ中かどうか
    CPoint m_ptStart;    // ドラッグ開始位置
    CPoint m_ptCurrent;  // 現在の位置
 
 public:
    CMyView() : m_bDragging(FALSE) {}
 
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
    afx_msg void OnMouseMove(UINT nFlags, CPoint point);
 
    DECLARE_MESSAGE_MAP()
 };
 
 void CMyView::OnLButtonDown(UINT nFlags, CPoint point)
 {
    // ドラッグ開始
    m_bDragging = TRUE;
    m_ptStart = point;
    m_ptCurrent = point;
 
    // マウスキャプチャを開始
    SetCapture();
 
    CView::OnLButtonDown(nFlags, point);
 }
 
 void CMyView::OnMouseMove(UINT nFlags, CPoint point)
 {
    if (m_bDragging) {
       // ドラッグ中の処理
       CClientDC dc(this);
 
       // 前回の矩形を消去 (XORモード)
       dc.SetROP2(R2_NOTXORPEN);
       dc.Rectangle(m_ptStart.x, m_ptStart.y,
                    m_ptCurrent.x, m_ptCurrent.y);
 
       // 新しい矩形を描画
       m_ptCurrent = point;
       dc.Rectangle(m_ptStart.x, m_ptStart.y,
                    m_ptCurrent.x, m_ptCurrent.y);
    }
 
    CView::OnMouseMove(nFlags, point);
 }
 
 void CMyView::OnLButtonUp(UINT nFlags, CPoint point)
 {
    if (m_bDragging) {
       // ドラッグ終了
       m_bDragging = FALSE;
 
       // マウスキャプチャを解放
       ReleaseCapture();
 
       // 最終的な矩形を確定
       CRect rect(m_ptStart, point);
       rect.NormalizeRect();
 
       // 矩形を描画
       DrawFinalRectangle(rect);
    }
 
    CView::OnLButtonUp(nFlags, point);
 }


※注意
マウスキャプチャを開始した場合、必ずReleaseCapture関数で解放する必要がある。
解放しないと、他のウィンドウがマウス入力を受け取れなくなる。

マウスキャプチャは、以下の用途で使用される。

  • ドラッグアンドドロップ操作
  • リサイズ操作
  • 描画ツールでのペン操作
  • スライダーやスクロールバーのドラッグ



マウストラッキング

マウストラッキングは、マウスカーソルがウィンドウに入った時や出た時、ホバーした時を検出する機能である。

TrackMouseEvent 関数を使用して、WM_MOUSELEAVE と WM_MOUSEHOVER メッセージを受信できる。

TrackMouseEvent関数

TrackMouseEvent関数は、マウストラッキングを開始する関数である。

関数のプロトタイプを以下に示す。

 BOOL TrackMouseEvent(LPTRACKMOUSEEVENT lpEventTrack);


TRACKMOUSEEVENT構造体を以下に示す。

 typedef struct tagTRACKMOUSEEVENT {
    DWORD cbSize;
    DWORD dwFlags;
    HWND  hwndTrack;
    DWORD dwHoverTime;
 } TRACKMOUSEEVENT, *LPTRACKMOUSEEVENT;


TRACKMOUSEEVENT構造体のメンバ
メンバ 説明
cbSize 構造体のサイズ
dwFlags トラッキングフラグ (TME_LEAVE、TME_HOVER等)
hwndTrack トラッキング対象のウィンドウハンドル
dwHoverTime ホバーまでの時間 (ミリ秒)


dwFlagsの値
フラグ 説明
TME_LEAVE マウスがウィンドウを離れた時にWM_MOUSELEAVEを送信
TME_HOVER マウスがホバーした時にWM_MOUSEHOVERを送信
TME_CANCEL トラッキングをキャンセル


WM_MOUSELEAVE / WM_MOUSEHOVER

  • WM_MOUSELEAVEメッセージ
    マウスカーソルがウィンドウのクライアント領域から出た時に送信される。
  • WM_MOUSEHOVERメッセージ
    マウスカーソルが指定時間静止した時に送信される。


使用例を以下に示す。

 class CMyView : public CView
 {
 protected:
    BOOL m_bTracking;  // トラッキング中かどうか
 
 public:
    CMyView() : m_bTracking(FALSE) {}
 
    afx_msg void OnMouseMove(UINT nFlags, CPoint point);
    afx_msg LRESULT OnMouseLeave(WPARAM wParam, LPARAM lParam);
    afx_msg LRESULT OnMouseHover(WPARAM wParam, LPARAM lParam);
 
    DECLARE_MESSAGE_MAP()
 };
 
 BEGIN_MESSAGE_MAP(CMyView, CView)
    ON_WM_MOUSEMOVE()
    ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
    ON_MESSAGE(WM_MOUSEHOVER, OnMouseHover)
 END_MESSAGE_MAP()
 
 void CMyView::OnMouseMove(UINT nFlags, CPoint point)
 {
    if (!m_bTracking) {
       // トラッキングを開始
       TRACKMOUSEEVENT tme;
       tme.cbSize = sizeof(TRACKMOUSEEVENT);
       tme.dwFlags = TME_LEAVE | TME_HOVER;
       tme.hwndTrack = m_hWnd;
       tme.dwHoverTime = 1000;  // 1秒
 
       if (TrackMouseEvent(&tme)) {
          m_bTracking = TRUE;
       }
    }
 
    CView::OnMouseMove(nFlags, point);
 }
 
 LRESULT CMyView::OnMouseLeave(WPARAM wParam, LPARAM lParam)
 {
    // マウスがウィンドウを離れた
    m_bTracking = FALSE;
 
    // ホバー効果を解除
    ClearHoverEffect();
 
    return 0;
 }
 
 LRESULT CMyView::OnMouseHover(WPARAM wParam, LPARAM lParam)
 {
    // マウスがホバーした
    // ツールチップやホバー効果を表示
    ShowTooltip();
 
    // 再度ホバーメッセージを受け取るには、TrackMouseEventを再度呼び出す
    m_bTracking = FALSE;
 
    return 0;
 }


マウストラッキングは、以下の用途で使用される。

  • ホバー効果の実装 (ボタンの色変更等)
  • ツールチップの表示
  • マウスがウィンドウを離れた時のクリーンアップ処理
  • カスタムコントロールの視覚フィードバック



マウス入力のシミュレーション

プログラムから仮想的にマウス入力を生成する技術を、マウス入力のシミュレーションという。

Windowsでは、SendInput 関数 と mouse_event 関数の2つの方法がある。

  • SendInput関数
    Windows 2000以降で推奨される方法である。
  • mouse_event関数
    レガシーな方法であり、互換性のために残されている。


SendInput関数

SendInput関数は、キーボードやマウスの入力イベントを合成する関数である。

関数のプロトタイプを以下に示す。

 UINT SendInput(
    UINT nInputs,
    LPINPUT pInputs,
    int cbSize
 );


引数 (パラメータ)
項目 説明
nInputs 入力イベントの数
pInputs INPUT構造体の配列
cbSize INPUT構造体のサイズ


INPUT構造体とMOUSEINPUT構造体を以下に示す。

 typedef struct tagINPUT {
    DWORD type;  // INPUT_MOUSE、INPUT_KEYBOARD等
    union {
       MOUSEINPUT    mi;
       KEYBDINPUT    ki;
       HARDWAREINPUT hi;
    };
 } INPUT, *PINPUT;
 
 typedef struct tagMOUSEINPUT {
    LONG    dx;
    LONG    dy;
    DWORD   mouseData;
    DWORD   dwFlags;
    DWORD   time;
    ULONG_PTR dwExtraInfo;
 } MOUSEINPUT, *PMOUSEINPUT;


MOUSEINPUTのメンバ
メンバ 説明
dx X方向の移動量 (または絶対座標)
dy Y方向の移動量 (または絶対座標)
mouseData ホイール回転量やボタンデータ
dwFlags イベントフラグ (MOUSEEVENTF_LEFTDOWN等)
time タイムスタンプ (0で自動設定)
dwExtraInfo 追加情報


dwFlagsの主要な値
フラグ 説明
MOUSEEVENTF_MOVE マウス移動
MOUSEEVENTF_LEFTDOWN 左ボタン押下
MOUSEEVENTF_LEFTUP 左ボタン解放
MOUSEEVENTF_RIGHTDOWN 右ボタン押下
MOUSEEVENTF_RIGHTUP 右ボタン解放
MOUSEEVENTF_MIDDLEDOWN 中ボタン押下
MOUSEEVENTF_MIDDLEUP 中ボタン解放
MOUSEEVENTF_WHEEL 垂直ホイール回転
MOUSEEVENTF_HWHEEL 水平ホイール回転
MOUSEEVENTF_ABSOLUTE dx, dyを絶対座標として扱う


使用例を以下に示す。

 void SendMouseClick(int x, int y)
 {
    INPUT inputs[3] = {};
 
    // マウス移動
    inputs[0].type = INPUT_MOUSE;
    inputs[0].mi.dx = x * (65535 / GetSystemMetrics(SM_CXSCREEN));
    inputs[0].mi.dy = y * (65535 / GetSystemMetrics(SM_CYSCREEN));
    inputs[0].mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
 
    // 左ボタン押下
    inputs[1].type = INPUT_MOUSE;
    inputs[1].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
 
    // 左ボタン解放
    inputs[2].type = INPUT_MOUSE;
    inputs[2].mi.dwFlags = MOUSEEVENTF_LEFTUP;
 
    // 入力イベントの送信
    UINT uSent = SendInput(3, inputs, sizeof(INPUT));
    if (uSent != 3) {
       AfxMessageBox(_T("SendInputに失敗しました"));
    }
 }


マウスホイール回転をシミュレートする例を以下に示す。

 void SendMouseWheelRotation(int delta)
 {
    INPUT input = {};
 
    input.type = INPUT_MOUSE;
    input.mi.mouseData = delta;  // 通常はWHEEL_DELTA (120) の倍数
    input.mi.dwFlags = MOUSEEVENTF_WHEEL;
 
    SendInput(1, &input, sizeof(INPUT));
 }
 
 // 使用例
 void CMyView::SimulateWheelUp()
 {
    SendMouseWheelRotation(WHEEL_DELTA);  // 上方向に1ノッチ
 }


mouse_event関数

mouse_event関数は、レガシーなマウス入力シミュレーション関数である。

関数のプロトタイプを以下に示す。

 VOID mouse_event(
    DWORD dwFlags,
    DWORD dx,
    DWORD dy,
    DWORD dwData,
    ULONG_PTR dwExtraInfo
 );


引数 (パラメータ)
項目 説明
dwFlags イベントフラグ
(MOUSEEVENTF_LEFTDOWN等)
dx X方向の移動量 (または絶対座標)
dy Y方向の移動量 (または絶対座標)
dwData ホイール回転量やボタンデータ
dwExtraInfo 追加情報
(通常は0)


使用例を以下に示す。

 void SendMouseClickLegacy(int x, int y)
 {
    // マウス移動
    mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,
                x * (65535 / GetSystemMetrics(SM_CXSCREEN)),
                y * (65535 / GetSystemMetrics(SM_CYSCREEN)),
                0, 0);
 
    // 左ボタン押下
    mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
 
    // 左ボタン解放
    mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
 }


※注意
mouse_event関数は、Windows 2000以降では非推奨とされている。
新規開発では、SendInput関数の使用を推奨する。

マウス入力のシミュレーションは、自動化ツール、テストツール、マクロツール、リモート操作ツールなどの実装に利用される。
ただし、セキュリティ上の理由から、使用には注意が必要である。


関連リンク