EternalWindows
再描画 / 描画情報の保存

前々節のプログラムはクリックした位置に長方形を描画するというものでしたが、 その描画内容は他のウインドウに覆われたときには消えてしまいました。 今回は、前々節のプログラムの描画内容を消えてないように見せる方法を 考えていきたいと思います。 下記のコードは前々節のプログラムのウインドウプロシージャです。

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg) {

	case WM_LBUTTONDOWN: {
		int x, y;
		HDC hdc;

		x = LOWORD(lParam);
		y = HIWORD(lParam);

		hdc = GetDC(hwnd);
		
		Rectangle(hdc, x, y, x + 70, y + 30);

		ReleaseDC(hwnd, hdc);

		return 0;
	}

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;
	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

このプロシージャで描画処理を行っているのは、 いうまでもなくWM_LBUTTONDOWNのReatangle呼び出しです。 しかし、前節の話からいくと描画コードはWM_PAINTに書かなければならなかったはずです。 何故なら、WM_PAINTで描画を行わないとクライアント領域の内容が消えたときに、 描画内容を修復できないからです。 となると、WM_LBUTTONDOWNに描画コードを書くこと事体、 既に的外れな考えだったのでしょうか。 実はそうでもないのです。

WM_PAINTで行うことはあくまで再描画です。 つまり、このコードで新しく何かを描画するのが目的なわけではなく、 消された内容を以前のものと同じように描き直すことが目的なのです。 ですから、描画処理がWM_PAINT以外で行われることは問題ではないのです (問題ではないのですが、できれば避けたいことでもあります)。 重要なのは、他のメッセージ内で何かを描画するならば、 WM_PAINTで描画内容を描き直せるだけのヒントを用意しておくことなのです。

上記のWM_LBUTTONDOWNでは長方形を描画しているものの、 長方形を描画した位置や数を変数などに全く保存していません。 これでは、WM_PAINTで再描画をするにしても長方形が描画されていた位置が分からないので、 再描画のしようがありません。 しかし、長方形の位置や数をきちんと保存しておけば、 WM_PAINTではその情報を頼りに再描画することができるはずです。

#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static int  nCount = 0;
	static RECT rc[10] = {0};

	switch (uMsg) {

	case WM_LBUTTONDOWN: {
		int x, y;
		HDC hdc;
		
		if (nCount >= 10) {
			MessageBox(hwnd, TEXT("長方形の描画は10個までです。"), TEXT("OK"), MB_OK);
			return 0;
		}

		x = LOWORD(lParam);
		y = HIWORD(lParam);

		SetRect(&rc[nCount], x, y, x + 70, y + 30);

		hdc = GetDC(hwnd);
		
		Rectangle(hdc, rc[nCount].left, rc[nCount].top, rc[nCount].right, rc[nCount].bottom);

		ReleaseDC(hwnd, hdc);
		
		nCount++;

		return 0;
	}

	case WM_PAINT: {
		int         i;
		HDC         hdc;
		PAINTSTRUCT ps;

		hdc = BeginPaint(hwnd, &ps);

		for (i = 0; i < nCount; i++)
			Rectangle(hdc, rc[i].left, rc[i].top, rc[i].right, rc[i].bottom);

		EndPaint(hwnd, &ps);

		return 0;
	}

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

早速、ウインドウプロシージャの内部を見ていきます。 まず、変数宣言からです。

static int  nCount = 0;
static RECT rc[10] = {0};

nCountは長方形の描画回数を保存するための変数です。 WM_PAINTではこの回数分、長方形を再描画することになります。 rcは描画した長方形の位置を保存します。 Rectangleの引数はRECT構造体のメンバと同じ意味を持っているので、 この構造体があればRectangleを呼び出す情報は揃っています。 配列の個数が10となっているのは、最大10個までの描画にしたかったからです。 別に何個でもよいのですが、取り敢えず10とします。 また、これらの変数が静的に宣言されていることも重要です。 これらの変数はWM_LBUTTONDOWNで更新され、WM_PAINTで参照されるため、 関数から制御が返っても、値は有効でなければなりません。

WM_LBUTTONDOWNではまず、描画回数の確認を行っています。

if (nCount >= 10) {
	MessageBox(hwnd, TEXT("長方形の描画は10個までです。"), TEXT("確認"), MB_OK);
	return 0;
}

rcの要素数は10であるため、rc[10]のようになることは許されません。 したがって、配列の範囲外アクセスのチェックは重要です。 次に重要なのは描画位置の保存です。

SetRect(&rc[nCount], x, y, x + 70, y + 30);

SetRectはRECT構造体を初期化する関数です。 この関数を使うことにより、

rc[nCount].left   = x;
rc[nCount].top    = y;
rc[nCount].right  = x + 70;
rc[nCount].bottom = y + 30;

のようなことをせずに済みます。 後、忘れてはいけないのが描画回数の更新です。

nCount++;

WM_LBUTTONDOWNの処理は前々節と比べて増えていますが、 個々のコードが行っていることは単純といえるはずです。 再描画の部分はさらに単純です。

for (i = 0; i < nCount; i++)
	Rectangle(hdc, rc[i].left, rc[i].top, rc[i].right, rc[i].bottom);

このコードは、正に再描画そのものです。 描画回数分、長方形を描画しているからでしょう。 WM_PAINTの目的は再描画ですから、nCountやrcの内容を変更するようなことは 絶対あってはいけません。


戻る