EternalWindows
GDI / ペン

今回は、ペンというGDIオブジェクトを取り上げます。 ブラシが図形の内部ならば、ペンは図形の枠の部分に相当します。 また、ペンは線や曲線を描画する関数にも利用することができます。 ペンはCreatePenで作成することができます。

HPEN CreatePen(
  int fnPenStyle,
  int nWidth,
  COLORREF crColor
);

fnPenStyleは、ペンのスタイルを指定します。 ここにはPS_XXXとして定義されている定数を指定することになります。 nWidthは、ペンの幅を指定します。 fnPenStyleにPS_SOLID(実線)を指定した場合、nWidthで幅を自由に決めることができます。 crColorは、ペンの色を指定します。 戻り値のHPENはペンを識別するハンドルです。

今回のプログラムは、デバイスコンテキストにペンを選択してRectangleを呼び出します。 また、デフォルトのブラシの色というものを強調するために、 クライアント領域の背景色を灰色に設定しています。

#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(GRAY_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 HPEN hpen = NULL;

	switch (uMsg) {

	case WM_CREATE:
		hpen = CreatePen(PS_SOLID, 3, RGB(0, 255, 0));
		return hpen != NULL ? 0 : -1;

	case WM_PAINT: {
		HDC         hdc;
		HPEN        hpenPrev;
		PAINTSTRUCT ps;

		hdc = BeginPaint(hwnd, &ps);
		
		hpenPrev = (HPEN)SelectObject(hdc, hpen);

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

		SelectObject(hdc, hpenPrev);

		EndPaint(hwnd, &ps);

		return 0;
	}

	case WM_DESTROY:
		if (hpen != NULL)
			DeleteObject(hpen);

		PostQuitMessage(0);

		return 0;

	default:
		break;

	}

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

プログラムを実行してみれば分かるように、長方形の枠に色が付いて太くなっています。 ペンの作成は、WM_CREATEで行われています。

case WM_CREATE:
	hpen = CreatePen(PS_SOLID, 3, RGB(0, 255, 0));
	return hpen != NULL ? 0 : -1;

これは前節の要領とよく似ています。 ブラシもペンもGDIオブジェクトなので呼び出す関数が違うだけで、 初期化するタイミングは同じです。 また、WM_DESTROYでDeleteObjectを呼び出すのも同じです。 そして、SelectObjectでGDIオブジェクトを選択するのも同じです。

hpenPrev = (HPEN)SelectObject(hdc, hpen);

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

SelectObject(hdc, hpenPrev);

このコードからも分かるように、GDIオブジェクトの使い方は非常に統一性があります。 なお、描画された長方形の内部が白いのは、デフォルトのブラシが白であるからです。 ブラシを選択していないかった場合は、デフォルトのブラシで図形の内部が塗りつぶされますから、 クライアント領域が灰色になっている今回では、その効果が目立ったわけです。 灰色のブラシを設定しているのは、WinMainの次のコードです。

wc.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);

hbrBackgroundメンバは、クライアント領域を塗りつぶすブラシを指定します。 GetStockObjectはシステムによって予め定義されているGDIオブジェクトを返す関数で、 GRAY_BRUSHと指定した場合は、灰色のブラシが返ることになります。 次の表は、GetStockObjectで取得できるGDIオブジェクトの一部を示しています。 なお、これらのオブジェクトはストックオブジェクトと呼ばれます。

定数 意味
WHITE_BRUSH 白のブラシ。
GRAY_BRUSH 灰色のブラシ。
NULL_BRUSH 空のブラシ(内部を塗りつぶさない)。
WHITE_PEN 白いペン。
ANSI_FIXED_FONT 固定幅システムフォント。

たとえば、白いペンを作成したいような場合は、 CreatePenではなく、GetStockObjectで簡単にペンを取得することができます。 また、GetStockObjectが返したGDIオブジェクトは、 DeleteObjectで削除する必要がないという利点があります。

GDIオブジェクトの使い方に慣れてたきたところで少し考えていただきたいことがあります。 それは、作成から選択、そして破棄という手順が煩わしくないかという点です。 次のコードは、前々節のプログラムのもので、文字の属性を変更しています。

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

文字の色というのはデバイスコンテキストの属性であるため、 この属性を変更するために作成や選択といった処理は不要でした。 GDIオブジェクトに作成や選択という処理がつきまとうのは、 一重にGDIオブジェクトがデバイスコンテキストから独立しているからなのですが、 それでは何故、独立する必要があったのでしょうか。 もし、独立していなかったならば、

SetBrushColor(hdc, RGB(255, 0, 0));
Rectangle(hdc, 10, 10, 60, 60);

というようなブラシの色を変更する関数が用意されているはずであり、 簡単にブラシの色を変更することができたかもしれないのです。 しかし、それでもGDIオブジェクトは独立すべきであったと思います。 何故なら、GDIオブジェクトは描画以外にも使い道があるからです。

たとえば、ビットマップ(HBITMAPで識別)というGDIオブジェクトがありますが、 このオブジェクトを取得するということが、描画するということに繋がるとは限りません。 HBITMAPはビットマップの幅や高さ、ビット数を管理しており、 HBITMAPを取得した側としては、それらの情報を表示したいだけなのかもしれません。 GDIオブジェクトもウインドウオブジェクト(ウインドウやメニュー、アイコンなど)のように、 それ自身の属性を管理するオブジェクトに過ぎません。 ただ、GDIオブジェクトにはデバイスコンテキストに選択できるという特典があるだけで、 本来の目的は描画ではなく(描画に使うことがほとんどなのは事実ですが)、 自身の属性をきちんと管理し、様々な場面で使えるようにすることが目的なのでしょう。

CreatePenIndirectについて

ペンの作成は、CreatePenIndirectでも行うことができます。

POINT  pt;
LOGPEN lp;

pt.x = 5;
lp.lopnStyle = PS_SOLID;
lp.lopnWidth = pt;
lp.lopnColor = RGB(255, 255, 0);

hpen = CreatePenIndirect(&lp);

CreatePenIndirectの呼び出しには、LOGPEN構造体を初期化する必要があります。 lopnStyleは、ペンのスタイルを指定します。 lopnWidthは、ペンの幅をPOINT構造体で指定します。 opnColorは、ペンの色を指定します。



戻る