EternalWindows
GDI / 楕円と長方形

今回から、様々な描画関数を紹介していきたいと思います。 ここで述べている描画関数とは、図形や直線を描画する関数のことで、 ビットマップと比べると見劣りする部分もありますが、 GDIオブジェクトと併用することにより見栄えを強化できます。 次に示す関数は、楕円を描画します。

BOOL Ellipse(
  HDC hdc, 
  int nLeftRect,
  int nTopRect,
  int nRightRect,
  int nBottomRect
);

hdcは、デバイスコンテキストのハンドルを指定します。 nLeftRectは、楕円に外接する長方形の左上隅のx座標を指定します。 nTopRectは、楕円に外接する長方形の左上隅のy座標を指定します。 nRightRecは、楕円に外接する長方形の右下隅のx座標を指定します。 nBottomRectは、楕円に外接する長方形の右下隅のy座標を指定します。

EllipseはRectangleと引数が同じであり、 差異といえば描画する図形が長方形であるか楕円であるかどうかでしょう。 つまり、Ellipseの使い方は既に習得しているわけであり、 また、単純に楕円を描画するだけのプログラムを作成しても芸がないため、 ここで少し関数ポインタの話題を取り上げたいと思います。

関数ポインタとは、その名の通り関数へのポインタであり、 たとえば、次のように使うことができます。

LPFNSHAPE lpfnShape;

lpfnShape = Ellipse;

lpfnShape(hdc, 100, 100, 300, 300);

lpfnShapeが正に関数ポインタであり、Ellipseのアドレスを指しています。 このようなときのlpfnShape呼び出しは、 実際にEllipseを呼び出したのと同じ結果が得られます。 lpfnShapeがEllipseのアドレスを指すには、lpfnShapeの型であるLPFNSHAPEが、 Ellipseのプロトタイプと同一である必要があります。

typedef BOOL (WINAPI *LPFNSHAPE)(HDC, int, int, int, int);

見て分かるようにLPFNSHAPEの定義は、Ellipseのプロトタイプと同一ですが、 このプロトタイプはRectangleのものと同一でもあります。 ということは、このLPFNSHAPE型の変数は、 EllipseとRectangleのどちらの呼び出しにも取って代わることができるのです。 これにより、コードに書いたlpfnShapeの呼び出しには、 楕円もしくは長方形の描画という2つの意味を持たせることができます。

今回のプログラムは、最初はクライアント領域に楕円を描画していますが、 マウスの左ボタンが押されると長方形が描画されます。 この呼び出すべき関数の切り替えを関数ポインタで行うことにより、 非常に効率のよいコードを書くことに成功しています。

#include <windows.h>

typedef BOOL (WINAPI *LPFNSHAPE)(HDC, int, int, int, int);

struct SHAPE {
	HPEN      hpen;
	HBRUSH    hbr;
	LPFNSHAPE lpfnShape;
};
typedef struct SHAPE SHAPE;
typedef struct SHAPE *LPSHAPE;

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   nShapeIndex = 0;
	static SHAPE shape[2] = {0};

	switch (uMsg) {

	case WM_CREATE:
		shape[0].hbr       = CreateSolidBrush(RGB(0, 255, 0));
		shape[0].hpen      = CreatePen(PS_SOLID, 3, RGB(0, 0, 255));
		shape[0].lpfnShape = Ellipse;

		shape[1].hbr       = CreateSolidBrush(RGB(0, 0, 255));
		shape[1].hpen      = CreatePen(PS_SOLID, 3, RGB(255, 255, 0));
		shape[1].lpfnShape = Rectangle;

		return 0;

	case WM_PAINT: {
		HDC         hdc;
		HPEN        hpenPrev;
		HBRUSH      hbrPrev;
		LPSHAPE     lpShape;
		PAINTSTRUCT ps;
		
		lpShape = &shape[nShapeIndex];

		hdc = BeginPaint(hwnd, &ps);

		hbrPrev  = (HBRUSH)SelectObject(hdc, lpShape->hbr);
		hpenPrev = (HPEN)SelectObject(hdc, lpShape->hpen);

		lpShape->lpfnShape(hdc, 100, 100, 300, 300);

		SelectObject(hdc, hbrPrev);
		SelectObject(hdc, hpenPrev);

		EndPaint(hwnd, &ps);
		
		return 0;
	}
	
	case WM_LBUTTONDOWN:
		nShapeIndex = nShapeIndex == 0 ? 1 : 0;
		InvalidateRect(hwnd, NULL, TRUE);
		return 0;

	case WM_DESTROY:
		DeleteObject(shape[0].hbr);
		DeleteObject(shape[0].hpen);
		
		DeleteObject(shape[1].hbr);
		DeleteObject(shape[1].hpen);

		PostQuitMessage(0);

		return 0;

	default:
		break;

	}

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

SHAPE構造体には、図形の描画に必要な情報が含まれています。 hpenは枠の描画に使うペンであり、hbrは図形の内部を塗りつぶすためのブラシです。 そして、lpfnShapeが描画すべき関数のアドレスを受け取ることになります。

static int   nShapeIndex = 0;
static SHAPE shape[2] = {0};

nShapeIndexは、SHAPE構造体のどちらの要素を使うかを示す添え字です。 shape[0]は楕円に関する情報、shape[1]が長方形に関する情報です。 shape[nShapeIndex].lpfnShapeとすることにより、 nShapeIndexを変更するだけで、呼び出すべき描画関数の変更が可能になります。

case WM_LBUTTONDOWN:
	nShapeIndex = nShapeIndex == 0 ? 1 : 0;
	InvalidateRect(hwnd, NULL, TRUE);
	return 0;

nShapeIndexが0のときは1にし、1のときは0にします。 そして、InvalidateRectでWM_PAINTを発行することにより、 nShapeIndexの変更のため、描画される図形の変化を確認できます。 既に描画されている図形を消去すべく、第3引数をTRUEにしているのが重要です。


戻る