オーナー描画ボタン


ボタンの動作を描画する

ボタンのなかで、より汎用的に利用できるボタンにオーナー描画ボタンがあります
オーナー描画ボタンは、あらゆる描画動作をプログラムでサポートします

これによって、ボタンとしての機能を持ったカスタム子ウィンドウを
簡単に、かつ確実に築くことができるのです

オーナー描画ボタンには、ボタンスタイルの BS_OWNERDRAW を指定します
このボタンが生成されると、最初に WM_MEASUREITEM メッセージが送られます
WM_MEASUREITEM はオーナー描画ボタン、コンボボックス、リストボックス、
またはメニュー項目のコントロールが生成されたときに、そのコントロールの親ウィンドウに送られます

WPARAM の値は、コントロールのIDになっています
メニュー の生成時によって送られたものであれば 0 になっています

LPARAM には MEASUREITEMSTRUCT 構造体へのポインタが格納されています
この構造体は次のような定義になっています
typedef struct tagMEASUREITEMSTRUCT {
    UINT  CtlType;
    UINT  CtlID;
    UINT  itemID;

    UINT  itemWidth;
    UINT  itemHeight;

    DWORD itemData;
} MEASUREITEMSTRUCT; 
CtlType には、コントロールのタイプを格納しています
これは、次の定数のいずれかの値になります

定数解説
ODT_BUTTON オーナー描画ボタン
ODT_COMBOBOX オーナー描画コンボボックス
ODT_LISTBOX オーナー描画リストボックス
ODT_LISTVIEW オーナー描画リストビューコントロール
ODT_MENU オーナー描画メニュー

今回行うのは、オーナー描画ボタンなので ODT_BUTTON が入っているはずです
他の項目に関してしては、後ほどの説明で明らかになるでしょう

CtlID メンバは、コントロールのIDが格納されています。メニューの場合は使いません
itemID メンバは、メニューのメニュー項目 ID 、または高さが可変のリスト ボックス
コンボ ボックスの項目 ID を指定します
高さが固定のリスト ボックス、コンボ ボックス、またはボタンでは使われません

itemWidth メンバにはメニュー項目の幅を指定するものです
itemHeight はリスト ボックス、コンボ ボックス、またはメニューの各項目の高さを指定します
itemData メンバは、コンボ ボックスやリスト ボックスなどで使うデータです
これら3つのメンバは、今回のオーナー描画ボタンで使うことはないでしょう

このメッセージを処理した時に返すべき値はとくにありません
通常、オーナー描画ボタンでこのメッセージを処理する必要はありません
#include <windows.h>

LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		CreateWindow(
			TEXT("BUTTON") , TEXT("") ,
			WS_CHILD | WS_VISIBLE | BS_OWNERDRAW ,
			0 , 0 , 400 , 100 ,
			hwnd , (HMENU)1 ,
			((LPCREATESTRUCT)(lp))->hInstance , NULL
		);
		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;
}


これがオーナー描画ボタンです
オーナー描画ボタンは背景をボタンの色で塗りつぶしています
しかし、この時点ではマウスやキーボードの入力には一切反応していません

オーナー描画ボタンを描画するのは、まさにオーナーウィンドウです
オーナー描画ボタンは、何らかの描画処理が必要になると
親ウィンドウに対して、再描画が必要であることを示すメッセージを送ります
このメッセージを処理し、私たちはクライアント領域に図を描画するのと同じように
ボタンの上に様々な図を描画することができるようになります

オーナー描画ボタンが再描画を必要としたとき
親ウィンドウは WM_DRAWITEM メッセージを受け取ります

このメッセージは WPARAM にコントロールの ID を持っています
また、LPARAM には DRAWITEMSTRUCT 構造体へのポインタを持ちます
この構造体は、描画に必要な様々なデータをメンバに保有しています
typedef struct tagDRAWITEMSTRUCT {
    UINT  CtlType; 
    UINT  CtlID; 
    UINT  itemID; 
    UINT  itemAction; 
    UINT  itemState; 
    HWND  hwndItem; 
    HDC   hDC; 
    RECT  rcItem; 
    DWORD itemData; 
} DRAWITEMSTRUCT;
CtlType には、コントロールのタイプが格納されています
これに格納されている値は、次の定数のいずれかになります

定数解説
ODT_BUTTON オーナー描画ボタン
ODT_COMBOBOX オーナー描画コンボ ボックス
ODT_LISTBOX オーナー描画リスト ボックス
ODT_MENU オーナー描画メニュー
ODT_LISTVIEW リスト ビュー コントロール
ODT_STATIC オーナー描画の静的コントロール
ODT_TAB タブ コントロール

CtlID メンバは、コントロールのIDです。メニューでは使いません

itemID はメニューのメニュー項目 ID、またはリスト ボックス
コンボ ボックスの項目のインデックスが格納されています
空のリストボックスやコンボボックスでは、このメンバは負数になります
オーナー描画ボタンでは、このメンバを使うことはないでしょう

itemAction メンバは、要求される描画動作を設定しています
これは、次の定数のいずれかになります

定数解説
ODA_DRAWENTIRE コントロール全体を描画する必要があるときに、このビットが設定されます
ODA_FOCUS コントロールが入力フォーカスを取得したり
失ったりしたときに、このビットが設定されます
itemState メンバをチェックして
コントロールがフォーカスを持っているかどうかを調べる必要があります
ODA_SELECT 選択状態のみが変更されたときに、このビットが設定されます
itemState メンバをチェックして、新しい選択状態を調べる必要があります

itemState は、現在の描画動作が行われたあとの
項目の表示状態に関する設定を指定することができます
値は、次の定数のいずれかになります

定数解説
ODS_CHECKED メニュー項目をチェックするときは、このビットが設定されます
このビットはメニューのみで使われます
ODS_DISABLED 項目が使用禁止状態で描画されるときは、このビットが設定されます
ODS_FOCUS 項目が入力フォーカスを持つときは、このビットが設定されます
ODS_GRAYED 項目が淡色表示されるときは、このビットが設定されます
このビットはメニューのみで使われます
ODS_SELECTED 項目が選択状態のときは、このビットが設定されます
ODS_COMBOBOXEDIT 描画は、オーナー描画コントロール ボックスの
選択フィールド (エディット コントロール) で行われます
ODS_DEFAULT 項目はデフォルト項目です

hwndItem は、コンボ ボックス、リスト ボックス
ボタンのコントロールのウィンドウ ハンドルを指定します
メニューでは、項目を持つメニューのハンドル (HMENU) を指定します

hDC は、コントロールのデバイスコンテキストを格納しています
コントロールに何らかの描画を行う時は、これを使う必要があります

rcItem は描画されるコントロールの境界を定義する四角形です
すなわち、コントロールのサイズです
Windows は、通常のコントロールの描画はデフォルトでクリップしています
つまり、この長方形の外に描画しようとしても、実際には描画されることはありません

しかし、メニュー項目の場合はクリップされないので
この長方形の外に描画しないように注意する必要があるのです
メニューに付いて、詳しくは後記しますので今は気にする必要はありません

itemData は、リストボックスやコンボボックスで使われる値です
今回のオーナー描画ボタンで使うことはありません

また、WM_DRAWITEM メッセージを処理したならば TRUE を返す必要があります
#include <windows.h>

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

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		CreateWindow(
			TEXT("BUTTON") , TEXT("") ,
			WS_CHILD | WS_VISIBLE | BS_OWNERDRAW ,
			0 , 0 , 400 , 100 ,
			hwnd , (HMENU)1 ,
			((LPCREATESTRUCT)(lp))->hInstance , NULL
		);
		return 0;
	case WM_DRAWITEM:
		hdc = ((LPDRAWITEMSTRUCT)(lp))->hDC;

		if (((LPDRAWITEMSTRUCT)(lp))->itemState & ODS_SELECTED)
			SelectObject(hdc , CreateHatchBrush(HS_DIAGCROSS , RGB(0xFF , 0 , 0)));
		else SelectObject(hdc , CreateHatchBrush(HS_DIAGCROSS , RGB(0 , 0 ,0xFF)));

		Rectangle(hdc , 0 , 0 ,
			((LPDRAWITEMSTRUCT)(lp))->rcItem.right ,
			((LPDRAWITEMSTRUCT)(lp))->rcItem.bottom
		);
		DeleteObject(SelectObject(hdc , GetStockObject(WHITE_BRUSH)));
		return TRUE;
	}
	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;
}


if (((LPDRAWITEMSTRUCT)(lp))->itemState & ODS_SELECTED) で状態を確認し
ボタンが押されている状態であれば、網を赤にし
そうでなければ、網を青くしてボタンを描画します

このほかにも、フォーカスを持っているかどうかを確認することもできます
通常、コントロールはフォーカスを持っている場合に何らかのアピールをするべきです
このプログラムは簡単のため、その処理は省略しています

もちろん、ボタンが持っている基本動作をサポートしているので
ボタンが押されたときに、親ウィンドウのキューは WM_COMMAND を受け取っています



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