マウスのドラッグの検出

マウスのドラッグ

マウスのドラッグ処理は、操作の中でよく行われる処理ですが、ドラッグを検出するメッセージは存在しません。そのため、既存のメッセージを組み合わせて作成する必要があります。ここでは、その方法について説明します。

サンプルプログラム

では実際にマウスのドラッグ処理を行うプログラムを見てみましょう。以下のプロジェクトを実行してみてください。

win32proj6-1:WinMain.cpp
ダウンロード
// ************************************
// Ex マウスのイベント処理を行う①
// ************************************
//必要なヘッダーファイルのインクルード
#define STRICT

#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <tchar.h>

// シンボル定義及びマクロ
#define WINDOW_WIDTH    800
#define WINDOW_HEIGHT   600

//文字列描画用配列
TCHAR   szstr[256] = _T("ボタンを押していません");

//	開始点
POINT  pt_start = { 0, 0 };
//	終了点
POINT pt_end = { 0, 0 };
//	ドラッグ中かどうかを示すフラグ
INT iDragFlag = 0;

//	インスタンス(グローバル変数)
HINSTANCE hInst;

//	ウィンドウプロシージャのコールバック関数
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpCmdLine,
	int nCmdShow)
{
	static TCHAR szWindowClass[] = _T("Sample06");
	static TCHAR szTitle[] = _T("マウスイベントを処理するプログラム②");

	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
	wcex.lpfnWndProc = WndProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wcex.lpszMenuName = NULL;
	wcex.lpszClassName = szWindowClass;
	wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));

	if (!RegisterClassEx(&wcex))
	{
		MessageBox(NULL,
			_T("RegisterClassExの処理に失敗しました"),
			_T("Sample06"),
			NULL);

		return 1;
	}

	hInst = hInstance; // グローバル変数に値を入れる

	// The parameters to CreateWindow explained:
	// szWindowClass				: アプリケーションの名前
	// szTitle						: タイトルバーに現れる文字列
	// WS_OVERLAPPEDWINDOW			: 生成するウィンドウのタイプ
	// CW_USEDEFAULT, CW_USEDEFAULT	: 最初に置くポジション (x, y)
	// WINDOW_WIDTH, WINDOW_HEIGHT	: 最初のサイズ (幅, 高さ)
	// NULL							: このウィンドウの親ウィンドウのハンドル
	// NULL							: メニューバー(このサンプルでは使用せず)
	// hInstance					: WinMain関数の最初のパラメータ
	// NULL							: WM_CREATE情報(このアプリケーションでは使用せず)
	HWND hWnd = CreateWindow(
		szWindowClass,
		szTitle,
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT,
		WINDOW_WIDTH, WINDOW_HEIGHT,
		NULL,
		NULL,
		hInstance,
		NULL
		);
	//	ウィンドウが生成できなかった場合
	if (!hWnd)
	{
		MessageBox(NULL,
			_T("ウィンドウ生成に失敗しました!"),
			_T("Sample06"),
			NULL);
		return 1;
	}

	// ウィンドウの表示に必要なパラメータ:
	// hWnd		: CreateWindowの戻り値
	// nCmdShow	: WinMainの引数の4番目
	ShowWindow(hWnd,
		nCmdShow);
	UpdateWindow(hWnd);

	// メインのメッセージループ:
	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return (int)msg.wParam;
}

//	ウィンドウプロシージャ(メッセージに対するコールバック関数)
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT ps;
	HDC hdc;
	switch (message)
	{
	case WM_PAINT:
		//	描画処理の開始
		hdc = BeginPaint(hWnd, &ps);
		// ドラッグ中だったら、始点から終点まで線を引く
		if (iDragFlag){
			MoveToEx(hdc, pt_start.x, pt_start.y, NULL);
			LineTo(hdc, pt_end.x, pt_end.y);
		}
		//	ペイント処理の終了
		EndPaint(hWnd, &ps);
		break;
		//キーを押した
		return 0;
		//左クリック
	case WM_LBUTTONDOWN:
		//	始点の位置の座標を取得
		pt_start.x = LOWORD(lParam);
		pt_start.y = HIWORD(lParam);
		//	ドラッグ中でなければ、終点もこの点に設定する。
		if (!iDragFlag){
			pt_end = pt_start;
			//	ドラッグ中かどうかのフラグを立てる
			iDragFlag = 1;
		}
		//	描画の更新
		InvalidateRect(hWnd, NULL, TRUE);
		break;
	case WM_LBUTTONUP:
		//	ドラッグ中かどうかのフラグを元に戻す
		iDragFlag = 0;
		InvalidateRect(hWnd, NULL, TRUE);
		break;
	case WM_LBUTTONDBLCLK:
		break;
	case WM_RBUTTONDOWN:
		break;
	case WM_RBUTTONUP:
		break;
	case WM_RBUTTONDBLCLK:
		break;
	case WM_MBUTTONDOWN:
		break;
	case WM_MBUTTONUP:
		break;
	case WM_MBUTTONDBLCLK:
		break;
	case WM_MOUSEMOVE:
		//	マウスが移動した
		if (iDragFlag){
			pt_end.x = LOWORD(lParam);
			pt_end.y = HIWORD(lParam);
			//再描画メッセージを発生させる
			InvalidateRect(hWnd, NULL, TRUE);
			return 0;
		}
		break;
		//キーを押した
	case WM_KEYDOWN:
		switch (wParam)
		{
		case VK_ESCAPE:
			//終了メッセージを発生させる
			PostMessage(hWnd, WM_CLOSE, 0, 0);
			break;
		}
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
		break;
	}
	return 0;
}

プログラムを実行すると、何も表示されないウィンドウが出現します。この状態から、ウィンドウ内の任意の位置をマウスでドラッグすると、最初にマウスをクリックした位置から、現在マウスカーソルが置かれた一まで、直線がひかれます。(図6-1.)

図6-1.ドラッグして線を引いた状態

ドラッグして線を引いた状態

手を離すと、線が消えます。プログラムは、右上の×ボタンを押すか、ESCキーを押せば、終了します。

プログラムの仕組み

フラグによる状態管理

では、具体的にプログラムの仕組みを見てみましょう。マウスのイベントに関しては、今まで行っていたものと変わりません。このサンプルで重要になるのは状態管理です。そのために、以下の変数が用意されています。(表6-1.)

表7-1.マウスのイベントの種類
変数名内容
pt_startドラッグの開始点(直線の開始点)
pt_endドラッグの終了点(ドラッグ中にマウスカーソルがある点)
iDragFlagマウスがドラッグ中であることを示すフラグ(0:ドラッグ中ではない、1:ドラッグ中)

処理の流れ

では、具体的にこれらの変数と、操作の関連性を見てみましょう。

①20~24行目で、初期状態として、pt_startおよび、pt_endの値は(0,0)、iDragFlagは、0で初期化されています。(図6-1.)

図6-1.初期状態

初期状態

②マウスがクリックされると、pt_startおよび、pt_endにはその座標が代入され、iDragFlagは1になります。(図6-2.)この処理が行われているのが、138行目から145目です。左ボタンを押すと、pt_endに現在の座標が入り、同時にiDragFlagの値が0だと、pt_startに同じ座標が入り、iDragFlagが1になります。

図6-2.最初にマウスがクリックされた状態

最初にマウスがクリックされた状態

③マウスがドラッグされると、pt_endに現在の座標が入り、pt_startまで直線がひかれます。(図6-3.)座標の更新と再描画の呼び出しは、170行目から176行目で行われます。iDragFlagが1であれば、pt_endの値に現在のマウスの位置が値として更新されます。

図6-3.マウスがドラッグされた状態

マウスがドラッグされた状態

④手を離すとiDragFlagに0が代入され、線が消えます。(図6-4.)LBUTTONDOWNイベントが発生すれば、マウスボタンから手を離した異なるので、この状態でiDragFlagに0を代入し、ドラッグ状態を解消します。

図6-4.マウスから手を離した状態

マウスから手を離した状態

線の描画

線の描画が行われているのがWM_PAINT内にある、MoveToEx関数、およびLineTo関数です。仕様は、それぞれ以下のようになっています。

MoveToEx関数
戻り値:BOOL型(int型と同等)

BOOL MoveToEx(
    HDC hdc,   // デバイスコンテキストのハンドル
    int X,        // 新しい現在の位置の x 座標
    int Y,        // 新しい現在の位置の y 座標
    LPPOINT    // それまでの現在の位置
);

描画の開始地点を指定します。MoveToEx 関数は、すべての描画関数に影響を及ぼします。

LineTo関数
戻り値:BOOL型(int型と同等)

BOOL LineTo(
    HDC hdc,    // デバイスコンテキストのハンドル
    int nXEnd,   // 終点の x 座標
    int nYEnd    // 終点の y 座標
);

直線の終点を指定します。線を引くには、具体的にはMoveToEx関数で最初の点、LineTo関数で最後の点を指定すると、その二点の間に直線が引かれます。