EternalWindows
GDI / 属性の保存

GDIオブジェクトを扱っていると、色々と煩わしい問題に直面することがあります。 たとえば、デバイスコンテキストにオブジェクトを選択したときには、 使い終えたら元に戻すという作業が必要になってきます。 これは、SelectObjectを複数回、呼び出す必要があるうえに、 元のオブジェクトを保存するための変数を宣言する手間も生じます。 この煩わしい手順はSaveDCとRestoreDCを呼び出せば解消することができます。 次のコードは、前節のWM_PAINTを新しくしたものです。

case WM_PAINT: {
	HDC         hdc;
	PAINTSTRUCT ps;

	hdc = BeginPaint(hwnd, &ps);

	SaveDC(hdc);
		
	SelectObject(hdc, hpen);
	SelectObject(hdc, hbr);

	Rectangle(hdc, 10, 10, 60, 60);

	RestoreDC(hdc, -1);

	EndPaint(hwnd, &ps);

	return 0;
}

デバイコンテキストに何らかのオブジェクトを選択する前に SaveDCを呼び出してデバイスコンテキストの現在の状態を保存します。 そして、デバイスコンテキストを開放する前にRestoreDCを呼び出すことによって、 デバイスコンテキストを元の状態に戻します。 第2引数の-1は直前に保存された状態へ戻すことを意味しています。

プライベートデバイスコンテキストを用いれば、 コードをさらに簡単に書くことができる可能性があります。 プライベートデバイスコンテキストとは1つのウインドウのために 専用のデバイスコンテキストを割り当てることです。 プライベートデバイスコンテキストの使用は、ウインドウクラスの初期化で指定します。

wc.style = CS_OWNDC;

WNDCLASSEX構造体のstyleメンバにCS_OWNDCを指定します。 これにより、GetDCやBeginPaintはプライベートデバイスコンテキストを返すことになります。 プライベートデバイスコンテキストに設定した属性は、 デバイスコンテキストを開放しても失われることはありません。

今回のプログラムはGDIオブジェクトの選択をWM_CREATEで行っています。 それにより、WM_CREATEに初期化コードが集中し、 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         = CS_OWNDC;
	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)
{
	switch (uMsg) {

	case WM_CREATE: {
		HDC    hdc;
		HBRUSH hbr;

		hdc = GetDC(hwnd);

		hbr = CreateSolidBrush(RGB(0, 255, 0));
		if (hbr == NULL)
			return -1;
		
		SelectObject(hdc, hbr);

		return 0;
	}

	case WM_PAINT: {
		HDC         hdc;
		PAINTSTRUCT ps;

		hdc = BeginPaint(hwnd, &ps);

		Rectangle(hdc, 10, 10, 60, 60);

		EndPaint(hwnd, &ps);

		return 0;
	}

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

このコードで面白いのは、ブラシを静的変数として宣言していないところです。 プライベートデバイスコンテキストに選択したオブジェクトは、 永続的であるため、WM_PAINTの処理でもその効果は続きます。 プライベートデバイスコンテキストはReleaseDCで開放する必要がないため、 WM_CREATEではReleaseDCを呼び出していません(呼び出しても問題ないと思われます)。

プライベートデバイスコンテキストには、いくつか気になることがあります。 まず、このデバイスコンテキストはいつ開放されるのでしょうか。 また、選択していたオブジェクトはデバイスコンテキストの開放とともに 削除されるのでしょうか。 さらに、このデバイスコンテキストのために使われるメモリの量はどれくらいなのでしょうか。 メモリといえば、冒頭で述べたSaveDCも無視できない話で、 この関数はコンテキストスタックと呼ばれるメモリ領域に属性をコピーしています。 結局のところ、これらのテクニックが本当の意味で優れているかどうは分からないため、 基本的にはこれまで通り描画の度にSelectObjectを呼び出して、 デバイスコンテキストの開放の前に元に戻すという方法が一番だと思います。


戻る