マウス操作
ウィンドウアプリはマウスで簡単に操作できるのが特徴のひとつです。
ここではマウス操作について説明します。
マウスボタン
マウスやキーボードのボタンは「押す」ものですが、プログラム的には「押して」「離す」という二つのメッセージが発生します。
マウスドラッグは「押してから離すまでの間」で判定します。
マウス左ボタンを押すとWM_LBUTTONDOWN
メッセージが、離すとWM_LBUTTONUP
メッセージが通知されます。
マウスボタンのメッセージは以下があります。
メッセージ | 説明 |
---|---|
WM_LBUTTONDOWN | マウス左ボタンを押す |
WM_LBUTTONUP | マウス左ボタンを離す |
WM_RBUTTONDOWN | マウス右ボタンを押す |
WM_RBUTTONUP | マウス右ボタンを離す |
WM_MBUTTONDOWN | マウス中ボタンを押す |
WM_MBUTTONUP | マウス中ボタンを離す |
簡単なサンプルコードです。
#include <windows.h>
#include <strsafe.h>
//ウィンドウの生成等は省略
#define BUFFERSIZE 128
//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rt;
static int l, r, c;
WCHAR buf[BUFFERSIZE];
static const WCHAR* format = L"クリック回数\n"
L"L: %d\n"
L"R: %d\n"
L"C: %d\n";
switch (message)
{
case WM_PAINT: //ウィンドウの描画
StringCchPrintf(buf, BUFFERSIZE, format, l, r, c);
GetClientRect(hWnd, &rt);
hdc = BeginPaint(hWnd, &ps);
DrawText(hdc, buf, -1, &rt, DT_WORDBREAK);
EndPaint(hWnd, &ps);
break;
case WM_LBUTTONUP: //マウス左クリック
l++;
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_RBUTTONUP: //マウス右クリック
r++;
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_MBUTTONUP: //マウス中クリック
c++;
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_DESTROY: //ウィンドウの破棄
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
このサンプルコードではボタンを離した時をクリックとして処理しています。
多ボタンマウス
標準的なマウスは3ボタンが多いですが、それ以上のボタンを備えたマウスもあります。
Windows APIでは5ボタンまでの入力に対応しています。
第四、第五ボタンの操作はWM_XBUTTONDOWN
とWM_XBUTTONUP
というメッセージで送られてきます。
どちらのボタンを押しても同じメッセージが通知されますが、ウィンドウプロシージャの第三引数WPARAMにボタン種類の情報が格納されています。
WPARAMにGET_XBUTTON_WPARAM
マクロを使用することで、操作したボタンを示す定数を取り出すことができます。
定数 | 説明 |
---|---|
XBUTTON1 | 第四ボタンの操作 |
XBUTTON2 | 第五ボタンの操作 |
#include <windows.h>
#include <strsafe.h>
//ウィンドウの生成等は省略
#define BUFFERSIZE 128
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rt;
static int x1, x2;
WCHAR buf[BUFFERSIZE];
static const WCHAR* format = L"クリック回数\n"
L"X1: %d\n"
L"X2: %d";
switch (message)
{
case WM_PAINT: //ウィンドウの描画
StringCchPrintf(buf, BUFFERSIZE, format, x1, x2);
GetClientRect(hWnd, &rt);
hdc = BeginPaint(hWnd, &ps);
DrawText(hdc, buf, -1, &rt, DT_WORDBREAK);
EndPaint(hWnd, &ps);
break;
case WM_XBUTTONUP: //マウス第四、第五ボタン
if (GET_XBUTTON_WPARAM(wParam) == XBUTTON1)
{
x1++;
}
else
{
x2++;
}
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_DESTROY: //ウィンドウの破棄
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
ダブルクリック
マウスボタンのダブルクリックは「WM_○BUTTONDBLCLK」というメッセージが通知されます。
メッセージ | 説明 |
---|---|
WM_LBUTTONDBLCLK | マウス左ボタンのダブルクリック |
WM_RBUTTONDBLCLK | マウス右ボタンのダブルクリック |
WM_MBUTTONDBLCLK | マウス中ボタンのダブルクリック |
WM_XBUTTONDBLCLK | マウス追加ボタンのダブルクリック |
ただしこれらはそのままでは通知されません。
ダブルクリックメッセージを使用するにはウィンドウクラスの登録時に、style
メンバにCS_DBLCLKS
フラグを追加する必要があります。
#include <windows.h>
#include <strsafe.h>
//省略
int APIENTRY wWinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPWSTR lpCmdLine,
int nCmdShow)
{
//省略
}
//ウィンドウクラスの登録
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
//wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = NULL;
return RegisterClassEx(&wcex);
}
//ウィンドウの作成
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
//省略
}
#define BUFFERSIZE 128
//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rt;
static int s, d;
WCHAR buf[BUFFERSIZE];
static const WCHAR* format = L"クリック回数\n"
L"Single:\t%d\n"
L"Double:\t%d";
switch (message)
{
case WM_PAINT: //ウィンドウの描画
StringCchPrintf(buf, BUFFERSIZE, format, s, d);
GetClientRect(hWnd, &rt);
hdc = BeginPaint(hWnd, &ps);
DrawText(hdc, buf, -1, &rt, DT_WORDBREAK | DT_EXPANDTABS);
EndPaint(hWnd, &ps);
break;
case WM_LBUTTONUP: //マウス左クリック
s++;
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_LBUTTONDBLCLK: //マウス左ダブルクリック
d++;
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_DESTROY: //ウィンドウの破棄
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
マウスホイール
マウスホイールの回転操作はWM_MOUSEWHEEL
メッセージが通知されます。
ホイールの水平操作(横スクロール)はWM_MOUSEHWHEEL
メッセージが通知されます。
(水平操作が可能なマウスのみ)
回転量はウィンドウプロシージャの第三引数WPARAMに格納されています。
実際の値を取り出すにはGET_WHEEL_DELTA_WPARAM
マクロを使用します。
#include <windows.h>
#include <strsafe.h>
//ウィンドウの生成等は省略
#define BUFFERSIZE 128
//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rt;
static int wheelV, wheelH;
WCHAR buf[BUFFERSIZE];
static const WCHAR* format = L"マウスホイール\n"
L"左クリックで値を初期化\n"
L"WheelV:\t%d\n"
L"WheelH:\t%d";
switch (message)
{
case WM_PAINT: //ウィンドウの描画
StringCchPrintf(buf, BUFFERSIZE, format, wheelV, wheelH);
GetClientRect(hWnd, &rt);
hdc = BeginPaint(hWnd, &ps);
DrawText(hdc, buf, -1, &rt, DT_WORDBREAK | DT_EXPANDTABS);
EndPaint(hWnd, &ps);
break;
case WM_LBUTTONUP: //マウス左クリック
//値を初期化
wheelV = wheelH = 0;
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_MOUSEWHEEL: //ホイール垂直回転
wheelV += GET_WHEEL_DELTA_WPARAM(wParam);
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_MOUSEHWHEEL: //ホイール水平操作
wheelH += GET_WHEEL_DELTA_WPARAM(wParam);
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_DESTROY: //ウィンドウの破棄
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
このコードでは左クリックで値を初期化するようにしています。
ホイール回転量はWindowsのコントロールパネルから変更可能ですが、ここで取得できる値はコントロールパネルの設定の影響を受けません。
マウス座標
現在のマウス座標(位置)は、マウス関連のメッセージが送られてきた時に、ウィンドウプロシージャの第四引数LPARAMに同時に送られてきます。
LPARAM型は整数値ですが、そのままでは座標としては扱えません。
X座標はGET_X_LPARAM
マクロで、Y座標はGET_Y_LPARAM
マクロでint型として取り出すことができます。
これらのマクロはwindowsx.h
のインクルードが必要です。
(windows.hとは別物)
#include <windows.h>
#include <windowsx.h>
#include <strsafe.h>
//ウィンドウの生成等は省略
#define BUFFERSIZE 128
//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rt;
static int x, y;
WCHAR buf[BUFFERSIZE];
static const WCHAR* format = L"マウス座標\n"
L"X: %d\n"
L"Y: %d";
switch (message)
{
case WM_PAINT: //ウィンドウの描画
StringCchPrintf(buf, BUFFERSIZE, format, x, y);
GetClientRect(hWnd, &rt);
hdc = BeginPaint(hWnd, &ps);
DrawText(hdc, buf, -1, &rt, DT_WORDBREAK | DT_EXPANDTABS);
EndPaint(hWnd, &ps);
break;
case WM_LBUTTONUP: //マウス左クリック
//lParamからマウス座標を取り出す
x = GET_X_LPARAM(lParam);
y = GET_Y_LPARAM(lParam);
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_DESTROY: //ウィンドウの破棄
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
このコードはクライアント領域のクリックした時の位置を表示します。
もしくはMAKEPOINTS
マクロを使用することでもLPARAMを座標に変換できます。
これらはLPARAMをPOINTS
構造体に変換します。
- typedef struct tagPOINTS {
SHORT x;
SHORT y;
} POINTS, *PPOINTS; - 座標情報を格納する構造体。
(SHORT版)
これはPOINT構造体のメンバがLONG型からSHORT型になっただけです。
POINTS points;
//省略
switch (message)
{
case WM_LBUTTONUP: //マウス左クリック
//lParamからマウス座標を取り出す
points = MAKEPOINTS(lParam);
InvalidateRect(hWnd, NULL, TRUE);
break;
}
マウス座標の取り出しにはLOWORD
マクロ(X座標)とHIWORD
マクロ(Y座標)を使用することも可能ですが、これは推奨されません。
これらのマクロは座標を符号なし整数(WORD型)として取り出しますが、座標がマイナスとなる場合(マルチディスプレイ環境など)で正しい値となりません。
値がマイナス値にならないことが確実な場合(クライアント領域のサイズの取得など)であればLOWORD
/HIWORD
マクロを使用しても問題ありません。
マウス座標を関数で取得/設定する
マウス座標はマウス関連のメッセージが送られてきたときに取得できますが、マウスメッセージに関係なくマウス座標を取得したい場合もあります。
そのような場合にはGetCursorPos
関数で取得できます。
また、SetCursorPos
関数でマウス座標を変更することもできます。
- BOOL GetCursorPos(
LPPOINT lpPoint
); - 現在のマウスカーソル座標をlpPointに格納する。
成功した場合は0以外を返す。
失敗した場合は0を返す。
- BOOL SetCursorPos(
int X,
int Y
); - 現在のマウスカーソル座標を(X, Y)に設定する。
成功した場合は0以外を返す。
失敗した場合は0を返す。
LPPOINT型はPOINT構造体変数のポインタ型です。
この関数で取得/設定される座標はスクリーン座標です。
つまり、ウィンドウではなく画面の左上を(0, 0)とした座標です。
#include <windows.h>
#include <strsafe.h>
//ウィンドウの生成等は省略
#define BUFFERSIZE 128
//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rt;
static POINT cursorPos;
WCHAR buf[BUFFERSIZE];
static const WCHAR* format = L"マウスのスクリーン座標\n"
L"キーボード操作で情報を更新\n"
L"左クリックでカーソルを右下にずらす\n"
L"X:\t%d\n"
L"Y:\t%d";
switch (message)
{
case WM_PAINT: //ウィンドウの描画
StringCchPrintf(buf, BUFFERSIZE, format, cursorPos.x, cursorPos.y);
GetClientRect(hWnd, &rt);
hdc = BeginPaint(hWnd, &ps);
DrawText(hdc, buf, -1, &rt, DT_WORDBREAK | DT_EXPANDTABS);
EndPaint(hWnd, &ps);
break;
case WM_KEYDOWN: //キー押下
GetCursorPos(&cursorPos);
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_LBUTTONUP: //マウス左クリック
GetCursorPos(&cursorPos);
SetCursorPos(cursorPos.x += 10, cursorPos.y += 10);
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_DESTROY: //ウィンドウの破棄
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
このコードはキーボードのキーを押下すると(どのキーでもOK)、現在のマウスカーソル位置の情報を更新します。
クライアント領域を左クリックするとマウスカーソル位置を少し右下にずらします。
スクリーン座標とクライアント座標の変換
スクリーン座標をクライアント領域の座標に変換したい場合はScreenToClient
関数を使用します。
- BOOL ScreenToClient(
HWND hWnd,
LPPOINT lpPoint
); - スクリーン座標lpPointをウィンドウhWndのクライアント座標に変換する。
成功した場合は0以外を返す。
失敗した場合は0を返す。
反対に、クライアント座標をスクリーン座標に変換したい場合はClientToScreen
関数を使用します。
- BOOL ClientToScreen(
HWND hWnd,
LPPOINT lpPoint
); - ウィンドウhWndのクライアント座標lpPointをスクリーン座標に変換する。
成功した場合は0以外を返す。
失敗した場合は0を返す。
どちらの関数も第二引数のPOINT構造体に新しい座標が格納されます。
ポインタ渡しなので注意してください。
マウスの移動
マウスを動かすとWM_MOUSEMOVE
メッセージが通知されます。
座標は他のマウスメッセージと同じく、LPARAMを調べることで取得できます。
#include <windows.h>
#include <windowsx.h>
#include <strsafe.h>
//ウィンドウの生成等は省略
#define BUFFERSIZE 128
//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rt;
static int x, y;
WCHAR buf[BUFFERSIZE];
static const WCHAR* format = L"マウスのスクリーン座標\n"
L"X:\t%d\n"
L"Y:\t%d";
switch (message)
{
case WM_PAINT: //ウィンドウの描画
StringCchPrintf(buf, BUFFERSIZE, format, x, y);
GetClientRect(hWnd, &rt);
hdc = BeginPaint(hWnd, &ps);
DrawText(hdc, buf, -1, &rt, DT_WORDBREAK | DT_EXPANDTABS);
EndPaint(hWnd, &ps);
break;
case WM_MOUSEMOVE: //マウス移動
x = GET_X_LPARAM(lParam);
y = GET_Y_LPARAM(lParam);
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_DESTROY: //ウィンドウの破棄
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
このコードは、クライアント領域上でマウスカーソルを移動させるとその座標を表示します。
座標はスクリーン座標です。
ちなみにWM_MOUSEMOVE
メッセージはマウスを動かしていないときでも送られてくることがあります。
例えばマウスカーソルがクライアント領域上にある状態でウィンドウのアクティブ状態が変化したときなどです。
マウスキャプチャ
カーソルがウィンドウ外(正確にはクライアント領域外)に移動するとマウスメッセージは通知されなくなります。
通常はウィンドウ外での操作は取得する必要はありませんが、マウスドラッグ中などはこれでは不都合な場合もあります。
ウィンドウ外でのマウス操作を取得する場合は、SetCapture
関数を使用します。
SetCapture関数の使用後はReleaseCapture
関数で元に戻します。
- HWND SetCapture(
HWND hWnd
); - ウィンドウhWndにマウスをキャプチャする。
戻り値は以前にマウスをキャプチャしていたウィンドウハンドル。
なければNULLを返す。
- BOOL ReleaseCapture();
- ウィンドウへのマウスキャプチャを解除する。
成功した場合は0以外を、失敗した場合は0を返す。
キャプチャとは「捕らえる」という意味で、ウィンドウ外でのマウスメッセージを「捕らえる」ことができます。
キャプチャ中はWM_LBUTTONDOWN
メッセージなどのボタンダウン以外のマウスメッセージがウィンドウプロシージャに送られるようになります。
ただしキャプチャは「カーソルがウィンドウ上にあるとき」または「マウスボタンを押したままカーソルがウィンドウ外に出たとき」にのみ有効です。
#include <windows.h>
#include <windowsx.h>
#include <strsafe.h>
//ウィンドウの生成等は省略
#define BUFFERSIZE 128
//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rt;
static int x, y;
static POINT points[2];
static char isDown;
WCHAR buf[BUFFERSIZE];
static const WCHAR* format = L"マウスのスクリーン座標\n"
L"X:\t%d\n"
L"Y:\t%d";
switch (message)
{
case WM_PAINT: //ウィンドウの描画
StringCchPrintf(buf, BUFFERSIZE, format, x, y);
GetClientRect(hWnd, &rt);
hdc = BeginPaint(hWnd, &ps);
Rectangle(hdc, points[0].x, points[0].y, points[1].x, points[1].y);
DrawText(hdc, buf, -1, &rt, DT_WORDBREAK | DT_EXPANDTABS);
EndPaint(hWnd, &ps);
break;
case WM_LBUTTONDOWN:
isDown = 1;
points[0].x = points[1].x = GET_X_LPARAM(lParam);
points[0].y = points[1].y = GET_Y_LPARAM(lParam);
SetCapture(hWnd);
break;
case WM_LBUTTONUP:
isDown = 0;
ReleaseCapture();
break;
case WM_MOUSEMOVE: //マウス移動
x = GET_X_LPARAM(lParam);
y = GET_Y_LPARAM(lParam);
if (isDown) {
points[1].x = x;
points[1].y = y;
}
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_DESTROY: //ウィンドウの破棄
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
このコードはクライアント領域でマウスをドラッグすると四角形を描画することができます。
ドラッグしたままカーソルがウィンドウ外へ出ても画面は更新され続けます。
もしマウスキャプチャをしないと、ドラッグ中にカーソルがウィンドウ外へと出たときにWM_MOUSEMOVE
メッセージが送られなくなり、画面が更新されなくなります。
またウィンドウ外でマウスボタンを離した場合にWM_LBUTTONUP
などのボタンアップメッセージも送られなくなるので、おかしな挙動となってしまいます。
なお、キャプチャ系の関数で操作できるのは現在のスレッドで作成されたウィンドウのみです。
マルチスレッドでの別スレッドや、別のプログラムで作成されたウィンドウはキャプチャ対象にできません。
GetCapture関数
GetCapture
関数はマウスキャプチャしているウィンドウのハンドルを取得します。
- HWND GetCapture();
- マウスをキャプチャしているウィンドウのハンドルを返す。
なければNULL。
ボタンのダウン状況を調べる
マウスメッセージの通知時、マウスボタンの状態や修飾キー(Ctrlキー、Shiftキー)の状態を調べることができます。
これにより例えば複数ボタンの同時押しや修飾キーによって処理を分岐させることができます。
他のキーの状態はウィンドウプロシージャのWPARAMに送られてきます。
これを定数でビットAND演算することでキーの状態を調べることができます。
(ビット演算に関しては→ビット演算の活用法)
定数 | 説明 |
---|---|
MK_SHIFT | Shiftキーが押されている |
MK_CONTROL | Ctrlキーが押されている |
MK_LBUTTON | マウス左ボタンが押されている |
MK_RBUTTON | マウス右ボタンが押されている |
MK_MBUTTON | マウス中ボタンが押されている |
MK_XBUTTON1 | マウス第四ボタンが押されている |
MK_XBUTTON2 | マウス第五ボタンが押されている |
例えば先ほどのマウスキャプチャのサンプルコードでは、マウスの状態を保存するためにstatic変数を用意していましたが、これを以下のように書き直すことができます。
static char isDown;
case WM_MOUSEMOVE: //マウス移動
x = GET_X_LPARAM(lParam);
y = GET_Y_LPARAM(lParam);
if (isDown) {
points[1].x = x;
points[1].y = y;
}
InvalidateRect(hWnd, NULL, TRUE);
//↓
case WM_MOUSEMOVE: //マウス移動
x = GET_X_LPARAM(lParam);
y = GET_Y_LPARAM(lParam);
if (wParam & MK_LBUTTON) { //マウス左ボタン
points[1].x = x;
points[1].y = y;
}
InvalidateRect(hWnd, NULL, TRUE);
break;
ただし、非クライアント領域で発生するメッセージ(WM_NCLBUTTONDOWN
メッセージなど)では、WPARAMにはこれとは異なる情報が格納されています。
非クライアント領域のメッセージは使用頻度が低いと思うので説明は省略します。