EternalWindows
ウインドウ管理 / 補助メモリ

アプリケーションの仕様によっては、ウインドウクラスやウインドウの作成で 設定した情報が任意のタイミングで必要になる場合があります。 そのようなときは、ウインドウハンドルを頼りにGetClassLongPtrや GetWindowLongPtr呼び出せば、目的の情報を得ることができます。

ULONG_PTR GetClassLongPtr(
    HWND hWnd,
    int nIndex
);

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

定数 意味
GCLP_HBRBACKGROUND 背景ブラシのハンドルを取得する。
GCLP_HCURSOR マウスカーソルのハンドルを取得する。
GCLP_STYLE ウインドウクラスのスタイルビットを取得する。
GCLP_WNDPROC ウィンドウプロシージャのアドレスを取得する。

ここで、GetClassLongPtrが役に立つ場面を紹介します。 今、アプリケーションが非常に時間の掛かる処理を行う段階に入るものとし、 それに応じてマウスカーソルを一時的に砂時計の形にしたいとします。 そのようなコードは恐らく、以下のようになると思われます。

HCURSOR hcur;

hcur = (HCURSOR)LoadImage(NULL, IDC_WAIT, IMAGE_CURSOR, 0, 0, LR_SHARED);
SetCursor(hcur);

// 時間の掛かる処理を行う

hcur = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
SetCursor(hcur);

このコードは、まずLoadImageの第2引数にIDC_WAITを指定することによって、 砂時計のカーソルのハンドルを取得し、それをSetCursorに指定します。 SetCursorは、現在のカーソルを第1引数で指定されたカーソルに変更するため、 この時点でカーソルは砂時計の形となります。 そして、時間の掛かる処理を終えた場合はカーソルを元に戻すために 矢印カーソル(IDC_ARROW)をロードしようとしていますが、 この処理は次のようなコードに置き換えることができます。

hcur = (HCURSOR)GetClassLongPtr(hwnd, GCLP_HCURSOR);

このようにすると、ウインドウクラスで設定したカーソルのハンドルが返ることになります。 これは、いってみれば既定のカーソルが返るのと同じことですから、 概念的に考えると矢印カーソルをロードして設定するよりも、 既定のカーソルを取得して設定する方が、カーソルを元に戻した感が強いといえます。

先の簡易コードは、砂時計カーソルを毎回ロードしていましたが、 これはあまり美しい処理とはいえないでしょう。 確かにLoadImageの第6引数にLR_SHAREDを指定した場合は、 2回目以降の呼び出しは同じハンドルが返るためメモリの浪費にはなりませんが、 一度カーソルをロードしてそれを必要に応じて取得する方が効率的なはずです。 これを成すには、ハンドルをグローバルに宣言するなど様々な方法がありますが、 ここではSetClassLongPtrを使った方法を紹介したいと思います。

ULONG_PTR SetClassLongPtr(
    HWND hWnd,
    int nIndex,
    LONG_PTR dwNewLong
);

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

SetClassLongPtrはクラス情報を新しく書き換えるほかに、 クラスメモリへのデータの設定としても使用できます。 クラスメモリは補助メモリとも呼ばれ、 アプリケーションが任意に使ってよいとされるメモリのことです。 次に例を示します。

SetClassLongPtr(hwnd, 0, lData);

この第2引数の0とは、補助メモリの先頭にデータを設定することを意味します。 つまりこの場合だと、補助メモリの先頭にlDataの値が格納されることになります。 ちなみに、補助メモリに2つ目のデータを設定したいような場合は、4とします。 これは、第3引数の型がLONGであるため、データを設定する境界が4バイト毎になり、 そのデータの位置を補助メモリの先頭からのオフセットで要求しているからです。

今回のプログラムは、先に示したSetClassLongPtrでカーソルのハンドルを設定し、 GetClassLongPtrでそのハンドルを取得します。

#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    = sizeof(HCURSOR);
	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)
{
	switch (uMsg) {

	case WM_CREATE: {
		HCURSOR hcur;

		hcur = (HCURSOR)LoadImage(NULL, IDC_WAIT, IMAGE_CURSOR, 0, 0, LR_SHARED);	
		SetClassLongPtr(hwnd, 0, (LONG)hcur);

		return 0;
	}

	case WM_LBUTTONDOWN: {
		HCURSOR hcur;
		
		hcur = (HCURSOR)GetClassLongPtr(hwnd, 0);
		SetCursor(hcur);

		Sleep(3000);
		
		hcur = (HCURSOR)GetClassLongPtr(hwnd, GCLP_HCURSOR);
		SetCursor(hcur);
		
		return 0;
	}

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

クラスメモリにデータを設定するうえで忘れてはならないのは、 ウインドウクラスの作成時にクラスメモリに必要なサイズを指定しておくことです。

wc.cbClsExtra = sizeof(HCURSOR);

クラスメモリのサイズは、cbClsExtraメンバに代入します。 今回は、カーソルのハンドルを設定しますから、sizeof(HCURSOR)で十分です。

case WM_CREATE: {
	HCURSOR hcur;

	hcur = (HCURSOR)LoadImage(NULL, IDC_WAIT, IMAGE_CURSOR, 0, 0, LR_SHARED);	
	SetClassLongPtr(hwnd, 0, (LONG)hcur);

	return 0;
}

砂時計カーソルをロードし、そのハンドルをクラスメモリに設定します。 これ以降、このハンドルはGetClassLongPtrで取得できます。

case WM_LBUTTONDOWN: {
	HCURSOR hcur;
	
	hcur = (HCURSOR)GetClassLongPtr(hwnd, 0);
	SetCursor(hcur);

	Sleep(3000);
	
	hcur = (HCURSOR)GetClassLongPtr(hwnd, GCLP_HCURSOR);
	SetCursor(hcur);
	
	return 0;
}

第2引数にSetClassLongPtrで指定した値を指定すれば、 クラスメモリに設定した値を取得できます。 Sleepは指定したミリ秒だけコードの進行を止める関数で、 時間の掛かる処理が思いつかなったため、この関数を代用することにしました。 マウスの左ボタンが押されると3秒間カーソルは砂時計の形となり、 3秒経つとウインドウクラスで設定した既定のカーソルに戻ります。

Windows98のInfo系関数

ウインドウに限らず、何らかの対象から情報を取得する必要性はよく生じるものです。 Windows98以降では、ウインドウやリソースなどの情報を取得するための関数群が提供されているため、 この機会に簡単に紹介しておきたいと思います。

関数 概要
GetCursorInfo グローバルカーソルの情報を取得する。
GetWindowInfo ウインドウ情報のスナップショットを取得する。
GetTitleBarInfo タイトルバーの位置と各ボタンの状態を取得する。
GetMenuBarInfo メニューの占める範囲やフォーカスの有無を取得する。
GetScrollBarInfo バーの範囲やつまみの位置を取得する。
GetComboBoxInfo エディットボックスとリストボックスのハンドルを取得する。
GetAltTabInfo Alt+Tabウインドウの項目の数を取得する。
GetListBoxInfo リストボックス内の項目の数を返す。

この中で特に魅力的といえる関数は、GetTitleBarInfoとGetAltTabInfoでしょう。 これらにはその処理の代わりとなる関数が存在せず、非常に重要な役割を担っています。 また、GetWindowInfoはウインドウ領域やクライアント領域のみならず、 ウインドウスタイルも取得できるため、 この関数一つでGetWindowRectとGetWindowLongPtrの役割を果たすことができます。



戻る