EternalWindows
再描画 / 伸縮

今回からは、RECT系の関数を利用した簡単なプログラムを作ります。 RECT系の関数はゲームでの座標判定やあたり判定などによく使われるので、 早いうちに習得しておくと役に立つことがあります。 今回紹介するInflateRectという関数は、RECT構造体を伸縮することができます。

BOOL InflateRect(
  LPRECT lprc,
  int dx,
  int dy
);

lprcは、伸縮の対象とするRECT構造体のアドレスを指定します。 dxは長方形の幅の増減量、dyは高さの増減量です。 マイナスを指定した場合は範囲が狭まることになります。 0を指定した場合は、伸縮することはありません。 次の簡易コードでrcがどう変化するかを見てみます。

RECT rc = {30, 90, 60, 140};

InflateRect(&rc, 10, -10);

この場合、幅の増減値は10となっているため、 rc.leftが20となり、rc.rightが70となります。 結果的に長方形の幅は(70 - 20)となり、最初の状態(60 - 30)より増えています。 高さの増減値は-10となっているため、 rc.topは100、rc.buttomは130となり、長方形の高さは (140 - 90)から(130 - 100)となり、減っていることが分かります。 InflateRectを使えばメンバを直接変更せず、直感的な変更が可能となるのです。

今回のプログラムは、長方形を連続で描画します。 まず、1つの長方形を普通に描画し、次にInflateRectで長方形を縮めます。 そして、その縮んだ長方形を描画します。

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

		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 + 100, y + 100);

		InvalidateRect(hwnd, &rc[nCount], FALSE);
		
		nCount++;

		return 0;
	}

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

		hdc = BeginPaint(hwnd, &ps);

		for (i = 0; i < nCount; i++) {
			CopyRect(&rctmp, &rc[i]);
			while (!IsRectEmpty(&rctmp)) {
				Rectangle(hdc, rctmp.left, rctmp.top, rctmp.right, rctmp.bottom);
				InflateRect(&rctmp, -5, -5);
			}
		}

		EndPaint(hwnd, &ps);

		return 0;
	}

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

WM_PAINTのfor分が少し入り組んだことになっています。 これは、長方形を連続で縮小させていくためです。 縮小はInflateRectの引数がマイナスになっていることで分かります。

for (i = 0; i < nCount; i++) {
	CopyRect(&rctmp, &rc[i]);
	while (!IsRectEmpty(&rctmp)) {
		Rectangle(hdc, rctmp.left, rctmp.top, rctmp.right, rctmp.bottom);
		InflateRect(&rctmp, -5, -5);
	}
}

CopyRectは、RECT構造体をコピーする関数です(rctmp = rc[i] としても問題ありません)。 コピーが必要なのは、実際の位置を格納するrc[i]を縮小してしまっては、 次の再描画の際に実際の位置に描画できなくなってしまうからです。 IsRectEmptyという関数は長方形が空であった場合、TRUEを返し、 そうでない場合はFALSEを返します。 rctmp.leftがrctmp.rightを上回るような場合は、この関数は長方形を空とみなします。 InflateRectを縮小していくと、leftとrightの値が互いに近くなるため、 やがてIsRectEmptyはTRUEを返し、ループから抜けます。


戻る