第96章 矩形の描画


突然ですが、この章では矩形(長方形)の描画について少し解説します。 どうして今頃矩形の描画をするのかというと、クリップボードに BMPを転送する場合、マウスでドラッグしてできる矩形部分のみ転送できれば 理想的ですね。マウスでドラッグ中はマウスカーソルの位置によって四角形の 大きさがリアルタイムに変化します。

考え方はいろいろあると思いますが、定石的には次のように行います。

0.出発点、ゴール、古いゴールを表す座標を記憶する変数の用意 1.左ボタンが押されたら出発点を記憶する 2.  このとき、古いゴールも出発点と同じにする 3.  四角形描画関数を呼ぶ 4.マウス移動中は現在のマウス位置をゴールに記憶 5.  (出発点,古いゴール)の四角形を消す 6.  (出発点,ゴール)の四角形を描画する 7.  古いゴールにゴールをコピーする 7.左ボタンが離されたら描画処理をやめる

これで、マウスでドラッグするとリアルタイムに大きさが変化する 四角形を描画できるはずです。このアルゴリズムを眺めると いくつか難関がありそうです。四角形を描画するのはいいとして これを消すにはどうしたらよいのでしょうか。これも昔から 定石があります。一番簡単なのはSetROP2関数により 描画モードをR2_NOTにしてしまいます。 こうすると四角形を描いて、もう一度同じ四角形を 描くとこれを消してしまいます。どうしてそうなるか?については 後の章で解説する予定です。(忘れなければね)

int SetROP2( HDC hdc, int fnDrawMode );

これで、描画モードをR2_NOTに設定します。

次に、座標を格納する構造体ですがPOINT構造体とかPOINTS構造体を 使います。POINTについてはすでに、第44章で解説してあります。 POINTS構造体については各メンバがSHORT型になっているという違いがあります。

typedef struct tagPOINTS { // pts SHORT x; SHORT y; } POINTS;

どうして、POINTとPOINTS構造体の2つがあるかすっきりしませんが、 どうも16ビット版と32ビット版の互換性のためにあるみたいです。

次に、処理すべきメッセージについて解説すると

WM_LBUTTONDOWN fwKeys = wParam; // キーフラグ xPos = LOWORD(lParam); yPos = HIWORD(lParam);

このメッセージはすでに、第88章で出てきていますが説明なしで 使っていました。このメッセージが来たらLPARAMを調べることで マウスの位置がわかります。また、WPARAMを調べることで 同時に押されているコントロールキーなどを知ることができます。 (WPARAMを調べるときちょっとした注意が必要です。必要が出てきたら 解説します。)

つぎにPOINTS構造体を簡単に作るマクロを紹介します。

POINTS MAKEPOINTS( DWORD dwValue );

従ってWM_LBUTTONDOWNメッセージを捕まえたとき、MAKEPOINT(lParam) でマウスの位置をPOINTS構造体に格納することができます。

さて、このくらいの予備知識があればマウスをドラッグして矩形を 描かせるプログラムを書くことができるでしょう。

// 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

これらは、別にどうと言うこともないメニューリソースですね。

// 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; }

ま、いつもと変わり映えしないです。自作でリソーススクリプトを 書いた人はresource.hをインクルードするのではなく自作の ヘッダファイル(シンボル定義)をインクルードします。(今までと同じ)

//ウィンドウ・クラスの登録 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; }

全く同じです。

//ウィンドウプロシージャ 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; }

メニューからIDM_STARTが選択されるとbStartがTRUEになります。 その状態でクライアント領域で左左ボタンを押すと(WM_LBUTTONDOWN) 描画中フラグ(bDraw)がTRUEにセットされます。そして、スタート、 古いゴールに現在のマウス位置がセットされます。 そして、矩形描画関数が呼ばれます。(スタート、古いゴール)の 四角形なのでこの時点では単なる点しか描画されません。 そのあと、カーソルを十文字に変えます。

HCURSOR SetCursor( HCURSOR hCursor );

これでマウスカーソルの形状を変えます。ここでは、簡単のために IDC_CROSSという既存のカーソルを使うことにしました。 余裕のある人は自作のカーソルを作ってみてください。

HCURSOR LoadCursor( HINSTANCE hInstance, LPCTSTR lpCursorName );

この関数は毎回登場してきますが、ちゃんと説明していませんでした。 自作でカーソルを作った(リソーススクリプトに記述)人は 最初の引数はインスタンスハンドルにします。既存のカーソルを 使う人は最初の引数をNULLにします。

さて、話が横道にそれましたがマウスのドラッグが始まったら (マウスが動いたら,WM_MOUSEMOVE)現在の座標をゴールにします。 そして(スタート,古いゴール)で四角形を描きます。 これで、以前の四角形は消されます。 次に、(すたーと、ゴール)で現在の四角形を描画します。 そして、描画し終わったら古いゴールに現在のゴールをコピーします。

左ボタンが離されたら(WM_LBUTTONUP)ドラッグの終了です。 スタートフラグ(bStart)、描画中フラグ(bDraw)をFALSEにセットします。 そしてカーソルを元の矢印に戻します。

こんな手順で行います。次に四角形の描画関数ですが 描画モードをR2_NOTにセットするだけで、後はそのまま 素直に書けばいいですね。

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; }

MoveToExやLineTo関数の第2,第3引数はint型ですが ここにshort型のデータをいれても問題はありません。


メニューから「描画開始」を選択してマウスでドラッグすると 四角形が描画されます。1つ描画すると再度メニューから 「描画開始」を選択しなくては次の四角形が描画されません。 また、プログラムを見てわかるようにウィンドウサイズを 変更したり、他のウィンドウに隠されて、再度表に出てきたときなど はせっかく描画した図形が消されてしまいます。 これらの不都合を改良してみてください。


[SDK Index] [総合Index] [Previous Chapter] [Next Chapter]

Update Dec/31/1997 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。