EternalWindows
再描画 / デバイスコンテキスト

Windowsアプリケーションでは、何らかのデータやオブジェクトをハンドルという 概念で表すことがよくあります。 たとえば、ウインドウを識別するウインドウハンドルは、 アプリケーションからウインドウについての内部構造を抽象化すると共に、 ハンドルを取得すればウインドウを制御できるという簡略化をもたらしました。 このような要領は、グラフィックスの描画についてもいえることであり、 グラフィックスを描画するにはデバイスコンテキストのハンドルが必要になります。 デバイスコンテキストは描画対象をアプリケーションから抽象化し、 様々な描画対象に対して同一の関数で描画を行えるという一貫性を提供します。 デバイスコンテキストのハンドルは、GetDCで取得することができます。

HDC GetDC(
  HWND hWnd
);

hwndは、ウインドウハンドルを指定します。 戻り値は、デバイスコンテキストのハンドルであり、 アプリケーションはそれをHDC型で識別します。

GetDCがウインドウハンドルを要求することから分かるように、 この関数が返すデバイスコンテキストは描画対象をウインドウとしています。 つまり、このデバイスコンテキストのハンドルを使えば、 ウインドウに対して描画を行えるということになります。 ただし、実際に描画を行える範囲はウインドウのクライアント領域です。 クライアント領域とは、ウインドウの枠やメニューバーを除いた白い部分のことで、 クライアント領域の左上隅の座標が描画関数にとっての原点となります。 今回は、長方形を描画するRectangleという関数について説明します。

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

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

デバイスコンテキストのハンドルは、使い終えたらReleaseDCで開放することになります。

int ReleaseDC(
  HWND hWnd,
  HDC hDC
);

hwndは、開放したいデバイスコンテキストの描画対象となっているウインドウのハンドルを指定します。 hDCは、開放したいデバイスコンテキストのハンドルを指定します。

今回のプログラムは、マウスの左ボタンが押されたときに描画処理を行います。

#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)
{
	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が送られてきます。 したがって、このメッセージのときに描画に関するコードを書くことになります。

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

WM_LBUTTONDOWNのlParamにはボタンを押したときのカーソルの位置が格納されているため、 それを描画位置とできるように変数に保存します。 +70というのは、長方形の幅を70ピクセルにしたいということを意味しています。 GetDCで取得したデバイスコンテキストは不要になったら開放しなければならないため、 メッセージの処理を終える前に必ずRelaseDCを呼び出さなければなりません。

さて、比較的容易に長方形を描画できたわけですが、 このプログラムには致命的な欠陥があります。 たとえば、このウインドウを他のウインドウで覆い隠した場合、 その隠された部分は真っ白になってしまったはずです。 最小化などをした際には、全ての長方形が消えてしまいます。 これは、単純にクライアント領域の内容というのものが、 一切保存されないようになっているためです。 この問題を解決する策を次節から考えていきます。


戻る