サブクラス化


ウィンドウプロシージャの切り替え

コントロールは、一般的にデフォルトの状態でもある程度使えますが
場合によっては、特殊な動作が要求されることもあります
とくに、商用のアプリケーションともなればデフォルトの状態の完成度では
コントロールの使い勝手が悪く、売り物にならないということもあります

よりアプリケーションの目的に合った動作をコントロールに求めるには
コントロールの一部のメッセージ処理を変更するということが望まれます

一般に、あるウィンドウクラスのウィンドウは全てが同じウィンドウプロシージャを使います
しかし、基本的な動作は全て同じだが、ある動作だけ異なるウィンドウを作りたい場合
これだけでは、多様性に欠けています

そこで、単一のウィンドウやコントロールにいわゆる個性を持たせることによって
特別な処理ができるようにする方法が ウィンドウのサブクラス化 です

コントロールにも、当然ウィンドウプロシージャが存在します
しかし、ウィンドウプロシージャの正確な位置はわかりませんでした
これは GetWindowLong() 関数で GWL_WNDPROC を指定して得ることができます

と、同時に SetWindowLong() という関数も存在するのです
この関数を用いればウィンドウプロシージャのすり替えができてしまいます

LONG SetWindowLong(HWND hWnd , int nIndex , LONG dwNewLong);

hWnd には設定するウィンドウやコントロールのハンドルを
nIndex は、どの値を設定するかを示す定数を指定します
dwNewLong は、新しく設定する値を指定します

関数が成功すれば、以前設定されていた値が返り、失敗すれば 0 が返ります
nIndex に指定する定数は GetWindowLong() 関数と同じです

これで、自作のウィンドウプロシージャをコントロールに採用させることができます
しかし、全ての処理を変更するというケースはごくまれでしょう
//それならば、ウィンドウクラスを定義して独自ウィンドウを作るべきだ

通常は、本来コントロールが持っているウィンドウプロシージャの機能に
独自の機能を上乗せする、または一部の処理を変更する程度のはずです
DefWindowProc() のように、興味のないメッセージは本来の機能にまかせたいものです
そこで、プロシージャを変更する前にあらかじめ GetWindowLong() 関数で
コントロールのウィンドウプロシージャのアドレスを取得しておきます

このアドレスを CallWindowProc() 関数で用いることで
興味のない処理部分を従来のウィンドウプロシージャにたくすことができます
LRESULT CallWindowProc(
	WNDPROC lpPrevWndFunc,
	HWND hWnd,
	UINT Msg,
	WPARAM wParam,
	LPARAM lParam
);
lpPrevWndFunc に、呼び出すウィンドウプロシージャのアドレスを指定します
それ以外は、WNDPROC 型の引数と同じなので省略します
#include <windows.h>

WNDPROC DefStaticProc;

LRESULT CALLBACK StaticProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
	switch(msg) {
	case WM_NCHITTEST:
		if (GetKeyState(VK_LBUTTON) < 0)
			MessageBox(
				hwnd , TEXT("Kitty on your lap") ,
				TEXT("Kitty") , MB_OK
			);
		return 0;
	}
	return CallWindowProc(DefStaticProc , hwnd , msg , wp , lp);
}

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

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		rect_box = CreateWindow(
			TEXT("STATIC") , TEXT("Kitty") ,
			WS_CHILD | WS_VISIBLE ,
			0 , 0 , 200 , 45 ,
			hwnd , (HMENU)1 ,
			((LPCREATESTRUCT)(lp))->hInstance , NULL
		);
		DefStaticProc = (WNDPROC)GetWindowLong(rect_box , GWL_WNDPROC);
		SetWindowLong(rect_box , GWL_WNDPROC , (LONG)StaticProc);
		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;
}


マウスを左クリックするとダイアログを表示するスタティックコントロールです
本来スタティックコントロールは入出力を受けつけていませんが
このように独自のウィンドウプロシージャを用いて処理させることができます

当然、本来の機能である描画などは従来のプロシージャが行っています

Windows Internet Explorer などのスクロールバーを右クリックするとポップアップが出たり
キーボードの上下キーでスクロールしたりするスクロールバーを作るには
このように、サブクラス化して機能を拡張する方法が取られます


SetWindowLong()

LONG SetWindowLong(HWND hWnd , int nIndex , LONG dwNewLong);

ウィンドウの属性を変更します

hWnd - ウィンドウのハンドルを指定します
nIndex - 変更する属性を表す定数を指定します
dwNewLong - 新しく設定する値を指定します

戻り値 - 成功すると前に設定されていた値、失敗すると 0

nIndex には次の値を指定します

定数解説
GWL_EXSTYLE 拡張ウィンドウスタイルを取得します
GWL_STYLE ウィンドウスタイルを取得します
GWL_WNDPROC ウィンドウプロシージャのアドレス
またはウィンドウプロシージャのアドレスを示すハンドルを取得します
GWL_HINSTANCE アプリケーションのインスタンスハンドルを取得します
GWL_HWNDPARENT アプリケーションのインスタンスハンドルを取得します
GWL_ID ウィンドウの ID を取得します
GWL_USERDATA ウィンドウに関連付けられた
アプリケーション定義の 32 ビット値を取得します

hWnd にダイアログボックスを指定している場合は
次の値を指定することもできます

定数解説
DWL_DLGPROC ダイアログボックスプロシージャのアドレスを設定します
DWL_MSGRESULT ダイアログボックスプロシージャ内で処理された
メッセージの戻り値を設定します
DWL_USER ハンドルやポインタなどの、アプリケーション固有の拡張情報を設定します

CallWindowProc()

LRESULT CallWindowProc(
	WNDPROC lpPrevWndFunc,
	HWND hWnd,
	UINT Msg,
	WPARAM wParam,
	LPARAM lParam
);
指定したウィンドウプロシージャにメッセージを渡します

lpPrevWndFunc - ウィンドウプロシージャのポインタを指定します
hWnd - メッセージを受け取るウィンドウのハンドルを指定します
Msg - メッセージを指定します
wParam - メッセージの追加情報を指定します
lParam - メッセージの追加情報を指定します

戻り値 - メッセージ処理の結果が返ります



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