【C/C++】ウィンドウ上の任意のクライアント領域にマウスカーソルが入ったときに処理【Win32】

【C/C++】ウィンドウ上の任意のクライアント領域にマウスカーソルが入ったときに処理【Win32】

ポイント1.マウスに関するあらゆるメッセージが発生する前に WM_NCHITTEST が発生する。
→ ウィンドウプロシージャで独自の処理ができる
ポイント2. lParam にマウスのスクリーン座標が入る
→ POINT pos = {LOWORD(lParam), HIWORD(lParam)};
→ ScreenToClient(hWnd, &pos);
→ で変換
ポイント3.ウィンドウプロシージャで return 0; してしまうと、他のマウスに関するメッセージが
一切起きなくなってしまう
→ return DefWindowProc(hWnd, message, wParam, lParam); する。

例1: 任意の範囲にマウスが入った時に非表示

ポイント1. ShowCursor 関数を使うが、単純に TRUE/FALSE で 表示/非表示になってくれない。
内部カウントの インクリメント/デクリメント として扱われる (0 以上 で 表示、 0 未満 で非表示)
単に ShowCursor(TRUE/FALSE); すると、マウスがそこにある間ずっとインクリメント/デクリメント
され続けることになり、思い通りの動作をしない。
→ while や for で条件を満たすまで繰り返す。
ポイント2. (2014/03/17追記) たいしたことではないですが、任意の範囲の ( (位置) + (大きさ) - 1 ) が
正確な上限であり、RECT 構造体の left, bottom には常に本当の座標より 1 大きい値が
入ると考えてよい (もちろん場合によるのだが
→ わざわざ 1 引くのも不恰好なので、整数なわけだし、上限には「 = 」を含まない不等号を使って判定
→ 例: (100 <= x) && (x < 200)
※コードを一部省略しています

// プロトタイプ宣言
LRESULT OnNCHitTest(HWND, UINT, WPARAM, LPARAM);

// 
// ウィンドウプロシージャ
// 
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {

switch (message) {
case WM_NCHITTEST:
return OnNCHitTest(hWnd, message, wParam, lParam);
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}

return DefWindowProc(hWnd, message, wParam, lParam);
}

// 
// WM_NCHITTEST が発生した時の処理
// 
LRESULT OnNCHitTest(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
POINT pos = {LOWORD(lParam), HIWORD(lParam)};

ScreenToClient(hWnd, &pos);

if ((5 <= pos.x) && (pos.x < 517) && (5 <= pos.y) && (pos.y < 517)) {
while (ShowCursor(FALSE) >= 0) {}
} else {
while (ShowCursor(TRUE) < 0) {}
}

return DefWindowProc(hWnd, message, wParam, lParam);
}

本当は 内部カウント 0/-1 だけ切り替えればいいから、 ShowCursor(TRUE)  して (0/1 が返るから) 返り値が true なら ShowCursor(FALSE) して false ならShowCursor(TRUE) する、みたいにしてもいいけど、このコードの関係上、新たに変数おく、もしくは if を二重にしないといけないのでやめました。深い理由はありません。

例2: 任意の範囲にマウスが入った時にカーソル変更 (2014/03/17追記)

ポイント1.ウィンドウクラスの hCursor にカーソルが指定されていると、クライアント領域などの
通常のカーソルになる範囲で、DefWindowProc によってカーソルが変更される。
かといって、マウスクリックとかを取得するために return 0; はしたくない。
→ hCursor = NULL にする
ポイント2.(例1) と同じく座標の判定
※コードを一部省略しています

// 
// ウィンドウクラス登録
// 
ATOM MyRegisterClass(HINSTANCE hInstance) {
WNDCLASSEX wcex;

wcex.cbSize = sizeof(WNDCLASSEX);

wcex.style= CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc= WndProc;
wcex.cbClsExtra= 0;
wcex.cbWndExtra= 0;
wcex.hInstance= hInstance;
wcex.hIcon= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TEST));
wcex.hCursor= NULL;
wcex.hbrBackground= (HBRUSH)GetStockObject(LTGRAY_BRUSH);
wcex.lpszMenuName= MAKEINTRESOURCE(IDC_TEST);
wcex.lpszClassName= szWindowClass;
wcex.hIconSm= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

return RegisterClassEx(&wcex);
}

// 
// ウィンドウプロシージャは同様
// 

// 
// WM_NCHITTEST が発生した時の処理
// 
LRESULT OnNCHitTest(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
POINT pos = {LOWORD(lParam), HIWORD(lParam)};

ScreenToClient(hWnd, &pos);

if ((5 <= pos.x) && (pos.x < 517) && (5 <= pos.y) && (pos.y < 517)) {
SetCursor(LoadCursor(NULL, IDC_SIZEALL));
} else {
SetCursor(LoadCursor(NULL, IDC_ARROW));
}

return DefWindowProc(hWnd, message, wParam, lParam);
}

LoadCursor で リソース もしくは システムのカーソル をロードし、 SetCursor します。

以下:日記

最初、 Cygwin か MinGW で Win32 アプリケーション作ってみたいなーって思ってたんですけど、どうしてもそれらについてくる DLL に依存してしまう らしいので、結局 VC 使うことにしました。
(Cygwin は古いバージョンだと依存しないようにできたらしいが今はできない)
(MinGW はうまくやればいくつかの DLL に依存しなくできるが、どうしてもはずせない DLL もあるっぽい)
OS開発のほうは、結局gcc/gas がよくわかってなくて中断中です…。
ではまたいつか~。
↓ブログランキング投票 (クリック) お願いします~