EternalWindows
コントロール基礎 / 新しいサブクラス化

前節では、SetWindowLongPtrを使用したサブクラス化の方法を説明しましたが、 今回はWindows XPから登場した新しいサブクラス化を使用する方法を説明します。 この方法は、従来のサブクラス化のようにウインドウプロシージャのアドレスを書き換えるのではなく、 独自のウインドウプロシージャを格納したサブクラスコールバックというものをウインドウに設定するため、 元のウインドウプロシージャを保存したり呼び出したりする作業は必要ありません。 サブクラスコールバックをウインドウに設定するには、SetWindowSubclassを呼び出します。

BOOL SetWindowSubclass(
  HWND hWnd,
  SUBCLASSPROC pfnSubclass,
  UINT_PTR uIdSubclass,
  DWORD_PTR dwRefData
);

hWndは、サブクラス化したいウインドウハンドルを指定します。 pfnSubclassは、新しいウインドウプロシージャのアドレスを指定します。 uIdSubclassは、サブクラスIDを指定します。 dwRefDataは、サブクラスコールバックに関連付けるデータを指定します。 サブクラスコールバックは、pfnSubclassとuIdSubclassで一意に識別されます。

ウインドウに設定したサブクラスコールバックを解除するには、RemoveWindowSubclassを呼び出します。 これにより、ウインドウへのメッセージは、既定のウインドウプロシージャに送られるようになります。

BOOL RemoveWindowSubclass(
  HWND hWnd,
  SUBCLASSPROC pfnSubclass,
  UINT_PTR uIdSubclass
);

hWndは、サブクラス化されたウインドウハンドルを指定します。 pfnSubclassは、ウインドウプロシージャのアドレスを指定します。 uIdSubclassは、サブクラスIDを指定します。

サブクラスコールバックのウインドウプロシージャは、SUBCLASSPROCという型を持ちます。 アプリケーションは、この型に準じたウインドウプロシージャを実装し、 SetWindowSubclassに指定することになります。

typedef LRESULT (CALLBACK *SUBCLASSPROC)(
  HWND hWnd,
  UINT uMsg,
  WPARAM wParam,
  LPARAM lParam,
  UINT_PTR uIdSubclass,
  DWORD_PTR dwRefData
);

hWndは、ウインドウハンドルが格納されます。 uMsgは、ウインドウメッセージが格納されます。 wParamは、WPARAM値が格納されます。 lParamは、LPARAM値が格納されます。 uIdSubclassは、サブクラスIDが格納されます。 dwRefDataは、サブクラスコールバックに関連付けられたデータが格納されます。

上記したウインドウプロシージャで独自に処理しないメッセージは、DefSubclassProcに指定することになります。

LRESULT DefSubclassProc(
  HWND hWnd,
  UINT uMsg,
  WPARAM wParam,
  LPARAM lParam
);

hWndは、サブクラス化されたウインドウハンドルを指定します。 uMsgは、ウインドウメッセージを指定します。 wParamは、WPARAM値を指定します。 lParamは、LPARAM値を指定します。

今回のプログラムは、SetWindowSubclassを呼び出してボタンをサブクラス化します。 SetWindowSubclassなどの関数を呼び出すには、commctrl.hのインクルードとcomctl32.libへのリンクが必要になります。

#include <windows.h>
#include <commctrl.h>

#define ID_BUTTON 100

#pragma comment (lib, "comctl32.lib")

LRESULT CALLBACK SubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);
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)
{
	static HWND hwndButton = NULL;

	switch (uMsg) {
		
	case WM_CREATE:
		hwndButton = CreateWindowEx(0, TEXT("BUTTON"), TEXT("ボタン"), WS_CHILD | WS_VISIBLE, 30, 30, 80, 40, hwnd, (HMENU)ID_BUTTON, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		SetWindowSubclass(hwndButton, SubclassProc, 1, 0);
		
		SetFocus(hwndButton);
		return 0;

	case WM_DESTROY:
		RemoveWindowSubclass(hwndButton, SubclassProc, 1);
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

LRESULT CALLBACK SubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
	switch (uMsg) {

	case WM_KEYDOWN:
		if (wParam == VK_RETURN)
			MessageBox(NULL, TEXT("ボタンがEnterキーで選択されました。"), TEXT("OK"), MB_OK);
		return 0;

	default:
		break;

	}

	return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}

SetWindowSubclassの第2引数は、独自のウインドウプロシージャのアドレスを指定します。 第3引数はサブクラスIDであり、今回は1を指定します。 第4引数はサブクラスコールバックに関連付けたい値であり、 ここに指定した値は第2引数のウインドウプロシージャの引数から参照することができます。 今回は特に渡すデータがないため、0を指定しています。 WM_DESTROYでは、RemoveWindowSubclassを呼び出していますが、 これは必ず必要というわけではありません。 子ウインドウが破棄される前に、元のウインドウプロシージャに戻そうと思っただけのことです。 RemoveWindowSubclassの第2引数と第3引数は、SetWindowSubclassのそれと同一になっていなければなりません。

SubclassProcには、ボタンに関する全てのメッセージが送られることになります。 ここでは、独自に処理したいメッセージだけを検出し、他のメッセージはDefSubclassProcに指定することで、 デフォルトの処理を行うようにします。 今回はWM_CREATEでSetFocusを呼び出すことにより、 ボタンにキーボードフォーカスを割り当てているため、 WM_KEYDOWNでキー入力の検出をするようにしています。 コントロールに対するキー入力は、親ウインドウにWM_COMMANDやWM_KEYDOWNで通知されないため、 こうした方法を用いる必要があります。


戻る