EternalWindows
再描画 / 無効領域

前節で問題となったのは、ウインドウのクライアント領域に 描画した内容が保存されていないという点です。 これを防ぐには、描画した内容をいかに保存するかを考えなければなりませんが、 残念ながらそのような方法はありません。 つまり、クライアント領域の内容を消されないようにするのは不可能なのです。 しかし、たとえ消されたとしても、消された瞬間にもう一度描画をしたら、 消されてないように見えるのではないでしょうか。 実はこの消された瞬間にもう一度描画するという作業こそが再描画であり、 必ず押さえておかなければならない内容です。

ウインドウのクライアント領域の内容が消されるということを 専用の言葉で述べると、無効領域の発生となります。 無効領域は正に消された部分を表しており、 ウインドウに無効領域が発生するとWM_PAINTというメッセージが送られます。 WM_PAINTをDefWindowProcに処理させた場合、 以下のようなコードが内部で実行されます。

hdc = BeginPaint(hwnd, &ps);
EndPaint(hwnd, &ps);

BeginPaintはデバイスコンテキストを返し、さらに無効領域を有効にします。 無効領域を有効にするとは、システムに対して、 ウインドウを再描画するということを伝えるようなものです。 無効領域が発生したままでは、延々とWM_PAINTが送られることになるので、 WM_PAINTでは必ず無効領域を有効にしなくてはなりません。 EndPaintはBeginPaintで取得したデバイスコンテキストのハンドルを開放します。 psというのはPAINTSTRUCT構造体なのですが、 この構造体のメンバについては後の節で取り上げます。

今回のプログラムは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)
{
	switch (uMsg) {

	case WM_PAINT: {
		HDC         hdc;
		RECT        rc;
		PAINTSTRUCT ps;

		hdc = BeginPaint(hwnd, &ps);

		GetClientRect(hwnd, &rc);
		Rectangle(hdc, rc.left, rc.top, rc.right / 2, rc.bottom / 2);

		EndPaint(hwnd, &ps);

		return 0;
	}

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

WM_PAINTで描画を行ったことによって、ウインドウを他のウインドウで覆ったり最小化したりしても、 クライアント領域に描画している長方形は常に表示されます。 くどいようですが、これはクライアント領域の内容が保存されているのではなく、 消されたときに再描画を行っているから表示され続けているように 見えているだけだということを忘れてはいけません。

GetClientRectという関数はクライアント領域の大きさを取得する関数です。 第2引数のRECT構造体は次のように定義されています。

typedef struct _RECT { 
  LONG left; 
  LONG top; 
  LONG right; 
  LONG bottom; 
} RECT, *PRECT; 

RECT構造体は長方形を表すための構造体で、 leftとtopは長方形の左上隅の座標を表すことになります。 rightとbottomは右下隅の座標です。 これは、Rectangleの引数と同じ要領です。 GetClientRectでRECT構造体を初期化した場合、leftとtopは常に0となります。 そのため、rightとbottomは右下隅の座標というよりも、 クライアント領域の幅と高さを表していると考えてもよいでしょう。 これらを2で割れば、長方形の右下隅がクライアント領域の中央のように見えます。

さて、WM_PAINTで描画処理を行うことにより、 クライアント領域の内容が消えてないように見せることができたわけですが、 まだまだ色々と気になる部分があります。 たとえば、今回のプログラムはデバイスコンテキストの取得をBeginPaintで行っていますが、 そうなると、前節で紹介したGetDCは何だったのか、という話になってきます。 現段階としては、WM_PAINTで描画コードを処理する場合はBeginPaintを呼び出し、 それ以外のタイミングで何かを描画したいときは、GetDCを呼び出すと考えてください。 BeginPaintとGetDCの本質的な違いは、後の節で説明します。


戻る