考え方はいろいろあると思いますが、定石的には次のように行います。
これで、マウスでドラッグするとリアルタイムに大きさが変化する 四角形を描画できるはずです。このアルゴリズムを眺めると いくつか難関がありそうです。四角形を描画するのはいいとして これを消すにはどうしたらよいのでしょうか。これも昔から 定石があります。一番簡単なのはSetROP2関数により 描画モードをR2_NOTにしてしまいます。 こうすると四角形を描いて、もう一度同じ四角形を 描くとこれを消してしまいます。どうしてそうなるか?については 後の章で解説する予定です。(忘れなければね)0.出発点、ゴール、古いゴールを表す座標を記憶する変数の用意 1.左ボタンが押されたら出発点を記憶する 2. このとき、古いゴールも出発点と同じにする 3. 四角形描画関数を呼ぶ 4.マウス移動中は現在のマウス位置をゴールに記憶 5. (出発点,古いゴール)の四角形を消す 6. (出発点,ゴール)の四角形を描画する 7. 古いゴールにゴールをコピーする 7.左ボタンが離されたら描画処理をやめる
これで、描画モードをR2_NOTに設定します。int SetROP2( HDC hdc, int fnDrawMode );
次に、座標を格納する構造体ですがPOINT構造体とかPOINTS構造体を 使います。POINTについてはすでに、第44章で解説してあります。 POINTS構造体については各メンバがSHORT型になっているという違いがあります。
どうして、POINTとPOINTS構造体の2つがあるかすっきりしませんが、 どうも16ビット版と32ビット版の互換性のためにあるみたいです。typedef struct tagPOINTS { // pts SHORT x; SHORT y; } POINTS;
次に、処理すべきメッセージについて解説すると
このメッセージはすでに、第88章で出てきていますが説明なしで 使っていました。このメッセージが来たらLPARAMを調べることで マウスの位置がわかります。また、WPARAMを調べることで 同時に押されているコントロールキーなどを知ることができます。 (WPARAMを調べるときちょっとした注意が必要です。必要が出てきたら 解説します。)WM_LBUTTONDOWN fwKeys = wParam; // キーフラグ xPos = LOWORD(lParam); yPos = HIWORD(lParam);
つぎにPOINTS構造体を簡単に作るマクロを紹介します。
従ってWM_LBUTTONDOWNメッセージを捕まえたとき、MAKEPOINT(lParam) でマウスの位置をPOINTS構造体に格納することができます。POINTS MAKEPOINTS( DWORD dwValue );
さて、このくらいの予備知識があればマウスをドラッグして矩形を 描かせるプログラムを書くことができるでしょう。
// rect.rcの一部 // 自分で作る人はwindows.hとシンボル(IDM_END, IDM_START)定義の // 自作ヘッダーファイルをインクルードしてください ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END MENUITEM "描画開始(&S)", IDM_START END
これらは、別にどうと言うこともないメニューリソースですね。// resource.hの一部 // 自前でシンボル定義のヘッダーファイルを作る人は参考にしてください #define IDM_END 40001 #define IDM_START 40002
ま、いつもと変わり映えしないです。自作でリソーススクリプトを 書いた人はresource.hをインクルードするのではなく自作の ヘッダファイル(シンボル定義)をインクルードします。(今までと同じ)// rect.cpp #define STRICT #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); void DrawRect(HWND, POINTS, POINTS); char szClassName[] = "rect"; //ウィンドウクラス POINTS start, end, old_end; short x, y; BOOL bDraw = FALSE; BOOL bStart = FALSE; int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
これもいつもと同じです。メニューの名前を忘れないでください。//ウィンドウ・クラスの登録 BOOL InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc;//プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; //インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); }
全く同じです。//ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかる四角形",//タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ NULL,//親ウィンドウのハンドル、親を作るときはNULL NULL,//メニューハンドル、クラスメニューを使うときはNULL hInst,//インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }
メニューからIDM_STARTが選択されるとbStartがTRUEになります。 その状態でクライアント領域で左左ボタンを押すと(WM_LBUTTONDOWN) 描画中フラグ(bDraw)がTRUEにセットされます。そして、スタート、 古いゴールに現在のマウス位置がセットされます。 そして、矩形描画関数が呼ばれます。(スタート、古いゴール)の 四角形なのでこの時点では単なる点しか描画されません。 そのあと、カーソルを十文字に変えます。//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; switch (msg) { case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0L); break; case IDM_START: bStart = TRUE; break; } break; case WM_LBUTTONDOWN: if(bStart) { bDraw = TRUE; old_end = start = MAKEPOINTS(lp); DrawRect(hWnd, start, old_end); SetCursor(LoadCursor(NULL, IDC_CROSS)); } break; case WM_MOUSEMOVE: if(bDraw) { end = MAKEPOINTS(lp); DrawRect(hWnd, start, old_end); DrawRect(hWnd, start, end); old_end = end; } break; case WM_LBUTTONUP: if(bDraw) { bDraw = FALSE; bStart = FALSE; SetCursor(LoadCursor(NULL, IDC_ARROW)); } break; case WM_CLOSE: id = MessageBox(hWnd, (LPCSTR)"終了してもよいですか", (LPCSTR)"終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0L; }
これでマウスカーソルの形状を変えます。ここでは、簡単のために IDC_CROSSという既存のカーソルを使うことにしました。 余裕のある人は自作のカーソルを作ってみてください。HCURSOR SetCursor( HCURSOR hCursor );
この関数は毎回登場してきますが、ちゃんと説明していませんでした。 自作でカーソルを作った(リソーススクリプトに記述)人は 最初の引数はインスタンスハンドルにします。既存のカーソルを 使う人は最初の引数をNULLにします。HCURSOR LoadCursor( HINSTANCE hInstance, LPCTSTR lpCursorName );
さて、話が横道にそれましたがマウスのドラッグが始まったら (マウスが動いたら,WM_MOUSEMOVE)現在の座標をゴールにします。 そして(スタート,古いゴール)で四角形を描きます。 これで、以前の四角形は消されます。 次に、(すたーと、ゴール)で現在の四角形を描画します。 そして、描画し終わったら古いゴールに現在のゴールをコピーします。
左ボタンが離されたら(WM_LBUTTONUP)ドラッグの終了です。 スタートフラグ(bStart)、描画中フラグ(bDraw)をFALSEにセットします。 そしてカーソルを元の矢印に戻します。
こんな手順で行います。次に四角形の描画関数ですが 描画モードをR2_NOTにセットするだけで、後はそのまま 素直に書けばいいですね。
MoveToExやLineTo関数の第2,第3引数はint型ですが ここにshort型のデータをいれても問題はありません。void DrawRect(HWND hWnd, POINTS beg, POINTS end) { HDC hdc; hdc = GetDC(hWnd); SetROP2(hdc, R2_NOT); MoveToEx(hdc, beg.x, beg.y, NULL); LineTo(hdc, end.x, beg.y); LineTo(hdc, end.x, end.y); LineTo(hdc, beg.x, end.y); LineTo(hdc, beg.x, beg.y); ReleaseDC(hWnd, hdc); return; }
メニューから「描画開始」を選択してマウスでドラッグすると
四角形が描画されます。1つ描画すると再度メニューから
「描画開始」を選択しなくては次の四角形が描画されません。
また、プログラムを見てわかるようにウィンドウサイズを
変更したり、他のウィンドウに隠されて、再度表に出てきたときなど
はせっかく描画した図形が消されてしまいます。
これらの不都合を改良してみてください。
Update Dec/31/1997 By Y.Kumei