EternalWindows
ウインドウ管理 / データの関連付け

前節で取り上げたGetClassLongPtrは、ウインドウクラスの情報を取得できましたが、 ウインドウの作成の際に指定したウインドウスタイルなどの情報は取得できませんでした。 GetWindowLongはこのような情報を取得することを専門とし、 ユーザーデータと呼ばれるメモリへのアクセスも提供します。

LONG_PTR GetWindowLongPtr(
  HWND hWnd,
  int nIndex
);

hWndは、ウインドウのハンドルを指定します。 nIndexは、取得したいウインドウの情報を示す定数を指定します。 戻り値は、nIndexで指定した定数で識別されるデータです。 nIndexに指定できる定数には、以下のようなものがありあす。

定数 意味
GWL_EXSTYLE 拡張ウインドウスタイル
GWL_STYLE ウインドウスタイル
GWLP_WNDPROC ウインドウプロシージャのアドレス
GWLP_HINSTANCE インスタンスハンドル
GWLP_USERDATA ウインドウに関連付けたいユーザーデータ

上記した定数で識別されるデータを変更したい場合は、 SetWindowLongPtrを呼び出します。

LONG_PTR SetWindowLongPtr(
  HWND hWnd,
  int nIndex,
  LONG_PTR dwNewLong
);

hWndは、ウインドウ情報を新しく設定したいウインドウのハンドルです。 nIndexは、どの情報を書き換えるかを示す定数で、 GetWindowLongPtrの第2引数と同じ値を指定できます。 dwNewLongは、新しく設定したい値です。

アプリケーションは、GWLP_USERDATAを利用することによって、 任意のデータをウインドウに関連付けることができます。 このようにした場合、ウインドウハンドルさえあれば いつでもそのデータをGetWindowLongで取得できることになりますから、 データを無作為にグローバルに宣言する必要はなくなります。 たとえば、WinMainで宣言されたWNDCLASSEX構造体をいつでも参照できるようにしたい場合は、 次のようなコードを記述します。

SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&wc);

SetWindowLongPtr第2引数にGWLP_USERDATAを指定しているため、第3引数のデータがウインドウに関連付けられたことになります。 そして、このデータを取得したくなった段階になると次のコードを実行します。

LPWNDCLASSEX lpwc = (LPWNDCLASSEX)GetWindowLongPtr(hwnd, GWLP_USERDATA);

GetWindowLongPtrの第2引数にGWLP_USERDATAを指定すれば、 SetWindowLongPtrで関連付けられたデータを取得できます。 これは、WNDCLASSEX構造体のアドレスでしたから、 戻り値をLPWNDCLASSEX型で受け取ることができます。

SetWindowLongPtrとGetWindowLongPtrを使用した方法は、 ウインドウに1つのデータしか関連付けることができません。 複数のデータを関連付けたい場合は、ウインドウのウインドウプロパティという領域にアクセスできるSetPropを呼び出します。

BOOL SetProp(
  HWND hWnd,
  LPCTSTR lpString,
  HANDLE hData
);

hWndは、ウインドウハンドルを指定します。 lpStringは、ウインドウプロパティに格納されたデータを識別する文字列を指定します。 hDataは、ウインドウプロパティに格納したいデータを指定します。

ウインドウプロパティでは、1つのデータを1つの文字列で識別しますから、 この文字列があればGetPropでデータを取得できます。

HANDLE GetProp(
  HWND hWnd,
  LPCTSTR lpString
);

hWndは、ウインドウハンドルを指定します。 lpStringは、ウインドウプロパティから取得したいデータを識別する文字列を指定します。 戻り値は、ウインドウプロパティに格納されたデータです。

ウインドウプロパティに格納されたデータは、 ウインドウが破棄される前にRemovePropで解除しておきます。

HANDLE RemoveProp(
  HWND hWnd,
  LPCTSTR lpString
);

hWndは、ウインドウハンドルを指定します。 lpStringは、解除したいデータを識別する文字列を指定します。

今回のプログラムは、ウインドウプロパティを使用する例を示しています。

#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(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;

	SetProp(hwnd, TEXT("msg"), &msg);
	SetProp(hwnd, TEXT("wndclassex"), &wc);

	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)
{
	switch (uMsg) {

	case WM_LBUTTONDOWN: {
		TCHAR szBuf[256];
		LPMSG lpmsg = (LPMSG)GetProp(hwnd, TEXT("msg"));

		wsprintf(szBuf, TEXT("%x"), lpmsg->message);
		MessageBox(NULL, szBuf, TEXT("メッセージ値"), MB_OK);

		return 0;
	}
	
	case WM_RBUTTONDOWN: {
		LPWNDCLASSEX lpwc = (LPWNDCLASSEX)GetProp(hwnd, TEXT("wndclassex"));

		MessageBox(NULL, lpwc->lpszClassName, TEXT("ウインドウクラス名"), MB_OK);

		return 0;
	}

	case WM_DESTROY:
		RemoveProp(hwnd, TEXT("msg"));
		RemoveProp(hwnd, TEXT("wndclassex"));
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

WinMainでは、SetPropを2回呼び出しています。 1回目の呼び出しでは、ウインドウプロパティにMSG構造体のアドレスを格納し、 2回目の呼び出しではWNDCLASSEX構造体のアドレスを格納しています。 そして、それぞれのデータを"msg"と"wndclassex"という文字列で識別しています。 よって、これらの文字列をGetPropに指定することで、 いつでも関連するデータを取得できます。 格納したデータをWM_DESTROYで解除するのを忘れないでください。

ウインドウプロパティを利用すれば、データを複数個関連付けることができますが、 その必要が本当にあるのかはよく検討するべきといえます。 たとえば、2つのデータがあったならば、それらを1つの構造体のメンバとすることによって、 関連付けるデータは1つで済むことになります。

その他のデータの関連付け

GetWindowLongPtrやGetPropで必要に応じてデータを取得する方法は、 その回数によっては煩わしさを感じることがあるかもしれません。 特にWindowProcでは、全てのメッセージがウインドウハンドルを参照できることから、 予めWM_CREATEでデータを取得しておき、それをWindowProc内で静的に 扱うようにしたほうが効率的というものでしょう。 しかし、そのような設計にするのであれば、CreateWindowExの最後の引数を利用すべきです。

CreateWindowEx(..., (LPVOID)&wc);

CreateWindowExの最後の引数は、CREATESTRUCT構造体のlpCreateParamsメンバに相当します。 CREATESTRUCT構造体は、WM_CREATEのlParamにそのアドレスが格納され、 先に述べたlpCreateParamsメンバを参照することによって、 アプリケーションが指定した独自のデータを参照できます。 たとえば、上記のコードではWNDCLASSEX構造体のアドレスを指定していますから、 WM_CREATEでWNDCLASSEX構造体を参照できるようになります。

static LPWNDCLASSEX lpwc = NULL;

switch (uMsg) {

case WM_CREATE: {
	LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam;
	
	lpwc = (LPWNDCLASSEX)lpcs->lpCreateParams;
	
	return 0;
}

このようにすればWindowProc内の各メッセージは、 WNDCLASSEX構造体へアクセスするためにデータのアドレスを取得する関数を呼び出す必要がなくなります。



戻る