EternalWindows
GDI / 描画属性

今回は、テキストを描画する関数を基に、 デバイスコンテキストの属性というものについて見ていきます。 テキストを描画するには、TextOutを呼び出します。

BOOL TextOut(
  HDC hdc,
  int nXStart,
  int nYStart,
  LPCTSTR lpString,
  int cbString
);

hdcは、デバイスコンテキストのハンドルを指定します。 nXStartは、テキストの開始位置のx座標を指定します。 nYStartは、テキストの開始位置のy座標を指定します。 lpStringは描画したいテキストを指定します。 cbStringは、lpStringの文字数を指定します。

さて、上記したTextOutには、テキストを描画するために必要な情報が 揃っているように思えますが、実際にはそうではありません。 たとえば、文字の色や文字の背景色はどうなるのでしょうか。 また、文字の大きさやフォントはどうなるのでしょうか。 実はこのような属性は、デバイスコンテキストに設定されており、 描画関数はデバイスコンテキストの属性を基に描画することになっているのです。

デバイスコンテキストを取得すると、デフォルトの属性が割り当てられます。 テキストに関する属性を例にとると、文字色は黒であり、背景色は白となっています。 この属性を変更すれば、描画関数はその変更を反映した結果になります。 そして、変更した属性は他の属性に変更するか、 または、デバイスコンテキストを開放するまで有効となります。 文字色はSetTextColorで変更が可能です。

COLORREF SetTextColor(
  HDC hdc,
  COLORREF crColor
);

hdcは、デバイスコンテキストのハンドルを指定します。 crColorは、設定する色を指定します。

COLORREFという型の正体はULONG、つまり符号なし32ビットです。 COLORREFはRGBを格納するためによく使われます。 RGBは、RGBマクロを使用してみれば意味がつかめやすくなります。

COLORREF cr = RGB(255, 0, 0);
	・
	・
	・
SetTextColor(hdc, cr);

RGBマクロの第1引数は赤(Red)の成分、第2引数は緑(Green)の成分、 第3引数は青(Blue)の成分です。 これらの引数に設定できるビット幅は8ビットであるため、 0から255までの値のいずれかを設定できます。 上の例では、赤の成分が255で、他の成分が0となっているため、 デバイスコンテキストの文字色の属性は赤となります。

今回のプログラムは、TextOutとSetTextColorを用いて、 属性が文字列の描画に反映されているのを確認します。

#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: {
		TCHAR       szText[] = TEXT("SampleText");
		HDC         hdc;
		PAINTSTRUCT ps;

		hdc = BeginPaint(hwnd, &ps);

		TextOut(hdc, 0, 0, szText, lstrlen(szText));
		
		SetTextColor(hdc, RGB(255, 0, 0));
		TextOut(hdc, 0, 30, szText, lstrlen(szText));

		SetTextColor(hdc, RGB(0, 0, 255));
		TextOut(hdc, 0, 60, szText, lstrlen(szText));

		EndPaint(hwnd, &ps);

		return 0;
	}

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

プログラムを実行すると3つのテキストが表示されているのが分かります。 最初のテキストを描画しているのは、次のコードです。

TextOut(hdc, 0, 0, szText, lstrlen(szText));

このTextOut呼び出しで文字色が黒となったのは、デバイスコンテキストの文字色の属性が、 デフォルトで黒となっているからです。 つまり、まだ文字色を変更する関数を呼び出していないので、デフォルトの色が参照されたわけです。 第5引数のlstrlenという関数はstrlen関数のWindows版で、文字列の長さが返ります。

SetTextColor(hdc, RGB(255, 0, 0));
TextOut(hdc, 0, 30, szText, lstrlen(szText));

SetTextColor(hdc, RGB(0, 0, 255));
TextOut(hdc, 0, 60, szText, lstrlen(szText));

これらのコードはTextOutの前にSetTextColorを呼び出しているので、 文字の色はSetTextColorで指定したものと矛盾がないはずです。 一度、設定した属性は変更したりするまで有効となるので、 2回目のSetTextColor呼び出しを削除した場合、 3つ目の文字列の色は赤となります。

今回のプログラムで覚えてかなければならないのは、 デバイスコンテキストの属性は、実際に描画を行う関数に指定するのではなく、 属性を設定する専用の関数を呼び出すということです。 こうした設計になっているのはおそらく、属性というものが主として永続的に続くことを願うからだと思われます。 たとえば、ゲームでは文字色を白とすることが多いですが、 この白という属性を毎回、TextOutに指定するのは煩わしいものです。 文字には色のほかに背景や透過モードという属性がありますが、 それらがデバイスコンテキストの属性であるおかげで、 hdcという引数だけで多くの属性を一辺に描画関数に指定できるのです。


戻る