ペンを作ろう


カレントペン

私たちがこれまで描画してきた図は、黒い外枠で描画されました
LineToなども、同様に黒くて細い線でしたね

これらは、デバイスコンテキストに設定されているペンと呼ばれる属性です
つまり、このペン属性を変更すれば線の色や太さを変えることができます

Windowsは、ペンやそのほかのいくつかの描画属性をデフォルトで用意しています
以前、クラスの登録で少しだけ触れたGetStockObject()関数で取得できます

Windowsは、デフォルトで定義済みのBLACK_PENが設定されています
これは、太さ1ピクセルの黒いペンです
同様に、定義済みのペンで白いWHITE_PENと描画しないNULL_PENがあります
これらも、GetStockObject()ファンクションから取得することができます

これらから取得したペンを属性に設定するにはSelectObject()を用います
後ほど説明する、ペン以外の属性もこれで設定することができます

HGDIOBJ SelectObject(HDC hdc , HGDIOBJ hgdiobj);

hdcには、デバイスコンテキストのハンドルを
hgdiobjは、設定する描画オブジェクトのハンドルを指定します
ここにペンを選択してあげればデバイスコンテキストはそのペンを使うようになります
このファンクションで設定されているペン属性をカレントペンとも呼びます<

戻り値は、成功すると以前のオブジェクトのハンドルが返り、失敗するとNULLになります
ただし、リージョンと呼ばれる属性を設定した場合は異なります(今は気にしなくて良い)

因みに、ペンオブジェクトのハンドル型はHPEN型です
HBRUSH同様に、キャストしてもかまいません(しなくてもかまいません)
#include <windows.h>

LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	HPEN hpen;
	PAINTSTRUCT ps;
	LPCTSTR lpctStr = TEXT("Kitty on your lap");

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hwnd , &ps);
		hpen = GetStockObject(WHITE_PEN);
		SelectObject(hdc , hpen);

		TextOut(hdc , 10 , 10 , lpctStr , lstrlen(lpctStr));
		Rectangle(hdc , 20 , 20 , 200 , 50);

		EndPaint(hwnd , &ps);
		return 0;
	}
	return DefWindowProc(hwnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow ) {
	HWND hwnd;
	MSG msg;
	WNDCLASS winc;

	winc.style		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= WndProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	winc.lpszMenuName	= NULL;
	winc.lpszClassName	= TEXT("KITTY");

	if (!RegisterClass(&winc)) return -1;

	hwnd = CreateWindow(
			TEXT("KITTY") , TEXT("Kitty on your lap") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hwnd == NULL) return -1;

	while(GetMessage(&msg , NULL , 0 , 0)) DispatchMessage(&msg);
	return msg.wParam;
}


表示した文字にかぶるように長方形を描画しています
SelectObject()によって、白いペンを選択しているため
外枠も白くなってしまったため、長方形を白で塗りつぶしています
GetStockObject()で白いペンを取得し、そのペンを設定していることが証明できました

プログラムになれた人ならば、SlectObject(hdc , GetStockObject(WHITE_PEN); と描くでしょう
もちろん、管理する変数が一つ減るのでこの方が簡易でよろしいですが
次のようにカスタムペンを用いる場合は問題が発生します


ペンの作成と削除

すでに定義されているペンは黒と白、そして何も描画しないペンしかありません
これ以外の色や太さのペンで描画するには、まずペンを作る必要があります

自分で作ったペンは、色や太さなどを自由に変えることができます
ペンオブジェクトを生成するにはCreatePen()ファンクションを用います

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

fnPenStyleは、ペンのスタイルを表す定数を指定します
通常の線はPS_SOLID、破線のPS_DASHなどがあります
PS_DASHなどの線は、ペンの幅が1以下の時しか適応しません

nWidthは、ペンの幅をピクセルで指定します
0を指定すると常に1ピクセルになります
crColorは、ペンの色を指定します。戻り値は設定したペンオブジェクトを返します

重要なことなのですが、ペンやブラシなどを総称してGDIオブジェクトといいます
カスタムで生成したGDIオブジェクトに共通することで、注意しなければならないことがあります

まず、作成したGDIオブジェクトは必ず削除しなければならいということです
デバイスコンテキスト同様に、ウィンドウプロシージャが終了する前にこの作業を行います
作成したGDIオブジェクトを破棄するにはDeleteObject()を呼び出します

BOOL DeleteObject(HGDIOBJ hObject);

hObjectに破棄するGDIオブジェクトを指定します
定義済みのストックオブジェクトは破棄する必要はありません
成功すると0以外、ハンドルが有効ではないなど、失敗すると0が返ります

ただし、決して選択中のGDIオブジェクトを削除してはいけません
有効はデバイスコンテキストに設定されているオブジェクトを消してはいけないのです
この二つに気を使いながら、GDIオブジェクトを扱う必要があります
#include <windows.h>

LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	HPEN hpen;
	PAINTSTRUCT ps;

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hwnd , &ps);
		hpen = CreatePen(PS_DASH , 0 , 0XFF << 16);
		SelectObject(hdc , hpen);
		Rectangle(hdc , 10 , 10 , 200 , 50);

		EndPaint(hwnd , &ps);
		DeleteObject(hpen);
		return 0;
	}
	return DefWindowProc(hwnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow ) {
	HWND hwnd;
	MSG msg;
	WNDCLASS winc;

	winc.style		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= WndProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	winc.lpszMenuName	= NULL;
	winc.lpszClassName	= TEXT("KITTY");

	if (!RegisterClass(&winc)) return -1;

	hwnd = CreateWindow(
			TEXT("KITTY") , TEXT("Kitty on your lap") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hwnd == NULL) return -1;

	while(GetMessage(&msg , NULL , 0 , 0)) DispatchMessage(&msg);
	return msg.wParam;
}


青い破線の1ピクセルのペンを作成しています
デバイスコンテキストを破棄してからペンを破棄していることに注目してください
何度も言いますが、有効なデバイスコンテキストが選択しているペンを破棄してはいけません
そのため、デバイスコンテキストを破棄してからペンを削除しています

もし、デバイスコンテキストを破棄する前にペンを削除したいならば
別のペンオブジェクトをデバイスコンテキストに選択させてから削除します

もう一つ、興味深い問題ですがこのような破線を描画した時
線と線の空白は一体何でしょう?
実はここには、背景色と背景モードによる属性の色が描画されます

ペンの作成には、これ以外にもCreatePenIndirect()ファンクションもあります
CreatePen()とは、引数が違うだけで内容は同じです

HPEN CreatePenIndirect(CONST LOGPEN *lplgpn);

ここで新しい型LOGPEN 型が現れました
これは構造体で、論理ペンを意味して意思ます
この構造体にペンのスタイルなどを設定し、その変数のポインタを渡します

LOGPEN型は、CreatePen()の引数と同じ意味の3つのメンバからなります
typedef struct tagLOGPEN { 
    UINT     lopnStyle; 
    POINT    lopnWidth; 
    COLORREF lopnColor; 
} LOGPEN;
lopnStyleは、ペンスタイルを
POINT構造体lopnWidthは、ペンの太さを指定します
この構造体変数はメンバ変数 x を使います(メンバ y は無視されます)
lopnColorは、ペンの色を設定します
#include <windows.h>

LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	static LOGPEN lopnPen;
	PAINTSTRUCT ps;

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		lopnPen.lopnStyle = PS_SOLID;
		lopnPen.lopnWidth.x = 5;
		lopnPen.lopnColor = 0XFF;
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hwnd , &ps);
		SelectObject(hdc , CreatePenIndirect(&lopnPen));
		Ellipse(hdc , 10 , 10 , 200 , 50);
		DeleteObject(SelectObject(hdc , GetStockObject(WHITE_BRUSH)));
		EndPaint(hwnd , &ps);
		return 0;
	}
	return DefWindowProc(hwnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow ) {
	HWND hwnd;
	MSG msg;
	WNDCLASS winc;

	winc.style		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= WndProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	winc.lpszMenuName	= NULL;
	winc.lpszClassName	= TEXT("KITTY");

	if (!RegisterClass(&winc)) return -1;

	hwnd = CreateWindow(
			TEXT("KITTY") , TEXT("Kitty on your lap") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hwnd == NULL) return -1;

	while(GetMessage(&msg , NULL , 0 , 0)) DispatchMessage(&msg);
	return msg.wParam;
}


SelectObject(hdc , CreatePenIndirect(&lopnPen)); と
DeleteObject(SelectObject(hdc , GetStockObject(WHITE_BRUSH))); に注目してください

HPEN型の変数を用意していないですが、どうやってペンオブジェクトを破棄するか問題ですね
このプログラムは、SelectObjectが以前設定されていたオブジェクトを返すという性質を利用しています


SelectObject()

HGDIOBJ SelectObject(HDC hdc , HGDIOBJ hgdiobj);

指定されたデバイスコンテキストに、指定されたオブジェクトを選択します

hdc - デバイスコンテキストのハンドルを指定します
hgdiobj - 選択するオブジェクトを指定します

戻り値 - 成功すると置き換えられる前のオブジェクトのハンドル、失敗するとNULL

リージョンを指定した場合は、次のいずれかが返ります

定数解説
SIMPLEREGION リージョンは単一の長方形です
COMPLEXREGION リージョンは単一の長方形よりも複雑な形です
NULLREGION リージョンは空です
GDI_ERROR 失敗

CreatePen()

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

指定されたスタイル、幅、色を持つ論理ペンを作成します

fnPenStyle - ペンのスタイルを表す定数を指定します
nWidth - ペンの幅を指定します crColor - ペンの色を指定します

戻り値 - 論理ペンのハンドルが返ります。失敗するとNULL

スタイルには次のいずれかの定数を指定します

PS_SOLID 実線のペンを作成します
PS_DASH 破線のペンを作成します
このスタイルは、ペンの幅がデバイス単位で 1 以下のときにだけ有効です
PS_DOT 点線のペンを作成します
このスタイルは、ペンの幅がデバイス単位で 1 以下のときにだけ有効です
PS_DASHDOT 一点鎖線のペンを作成します
このスタイルは、ペンの幅がデバイス単位で 1 以下のときにだけ有効です
PS_DASHDOTDOT 二点鎖線のペンを作成します
このスタイルは、ペンの幅がデバイス単位で 1 以下のときにだけ有効です
PS_NULL 空のペンを作成します
描画は行われません
PS_INSIDEFRAME 実線のペンを作成します
境界長方形を指定するRectangle()やEllipse()などでこのペンを使うと
その長方形に完全に収まるように図形が縮小されます
ジオメトリックペンにだけ有効です

CreatePenIndirect()

HPEN CreatePenIndirect(CONST LOGPEN *lplgpn);

LOGPEN構造体の論理ペンのハンドルを返します

lplgpn - LOGPEN型構造体へのポインタを指定します

戻り値 - 論理ペンのハンドルが返ります。失敗するとNULL

DeleteObject()

BOOL DeleteObject(HGDIOBJ hObject);

ペン、ブラシ、フォント、ビットマップ、リージョン、パレットのいずれかのオブジェクトを削除し
オブジェクトに関連付けられていたシステムリソースをすべて解放します
オブジェクトを削除すると、ハンドルは無効になります

hObject - GDIオブジェクトのハンドルを指定します

戻り値 - 成功すると0以外、失敗すると0が返ります



前のページへ戻る次のページへ