EternalWindows
基礎 / メッセージの戻り値

前節ではWM_LBUTTONDOWNが送られるタイミングについて説明しましたが、 ウインドウに送られるメッセージはこれ以外にも相当な数と種類が存在します。 幸いにもアプリケーションはそれらの処理をDefWindowProcに任せることができるため、 自分にとって必要なメッセージを取捨選択すればそれで済むことになりますが、 たとえ処理するつもりがなくても、どのようなメッセージがどのタイミングで送られるかを 知っていれば、それだけアプリケーション開発の幅も増えることになるはずです。 今回は、多くのアプリケーションにとって有用になるであろうメッセージ及び、 メッセージ処理の基本について説明したいと思います。

case WM_LBUTTONDOWN:
	MessageBox(hwnd, TEXT("左ボタンが押されました。"), TEXT("OK"), MB_OK);
	return 0;

このコードは前節のWM_LBUTTONDOWNの処理ですが、0を返しているところに注目してください。 一般にウインドウプロシージャの戻り値とは、メッセージの処理結果をシステムに伝えるものであり、 システムはその結果に応じて独自の処理を行ったり新しいメッセージを送ったりしています。 つまり、システムはウインドウにメッセージを送ったらそれで終わりというわけではなく、 メッセージを送った後もアプリケーションと何らかの交渉をしているのです。 先に述べたように、アプリケーションはメッセージの処理結果を返すことができますから、 戻り値を通じてその交渉(メッセージの流れ)をある程度制御することができます。

それでは、WM_LBUTTONDOWNで0を返した場合、システムはどのような処理を行うのでしょうか。 リファレンスによれば、このメッセージをアプリケーションが処理した場合には0を返すべきであるという記述だけで、 それ以上のことについては書かれていません。 実は、戻り値が重要となるメッセージというのはむしろ稀なぐらいで、 単純にシステムからの要求や通知の意味だけを持つメッセージが大半を占めています。 しかし、だからといって何でも好きな値を返してよいというわけでもないため、 きとんとリファレンス通りの値を返す習慣をつけておくべきでしょう。

今回のプログラムは、戻り値が重要となる2つのメッセージを処理します。 また、その戻り値の決定をユーザーに委ねるため、MessageBoxの応答結果を確認します。

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

	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: {
		int nId;

		nId = MessageBox(NULL, TEXT("ウインドウの生成を許可しますか"), TEXT("WM_CREATE"), MB_YESNO);
		if (nId == IDYES)
			return 0;
		else
			return -1;
	}
	
	case WM_CLOSE: {
		int nId;

		nId = MessageBox(NULL, TEXT("終了してよろしいですか"), TEXT("WM_CLOSE"), MB_YESNO);
		if (nId == IDYES)
			break;
		else
			return 0;
	}

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

プログラムを実行するとウインドウを作成するかどうかを 尋ねるメッセージボックスが表示されます。 また、ウインドウを閉じようとしたときには、終了確認が行われます。 これらはメッセージの戻り値を巧みに扱って実装されています。

case WM_CREATE: {
	int nId;

	nId = MessageBox(NULL, TEXT("ウインドウの生成を許可しますか"), TEXT("WM_CREATE"), MB_YESNO);
	if (nId == IDYES)
		return 0;
	else
		return -1;
}

WM_CREATEは、ウインドウの作成過程で送られるメッセージです。 過程という言葉は、このメッセージがウインドウ作成の決定権を持つことを意味します。 実は、このメッセージが送られている時点でウインドウは作成されています。 しかし、このメッセージが-1を返した場合はウインドウが破棄され(WM_DESTORYが送られる)、 WinMain関数のCreateWindowExの戻り値はNULLとなります。 せっかく、ウインドウが作成されているのに わざわざ-1を返すことなどあるのかと思うかもしれませんが、 WM_CREATEをプログラムの初期化場所と考えれば頷けることです。

たとえば、ゲームのようなアプリケーションでは、 画像ファイルのロードやセーブデータのロードなどは必須の処理ですが、 万が一ファイルが存在しないような場合はロードが失敗してしまいます。 このような場合は、ゲームを正常に実行できないわけですから、 ウインドウを作成する必要はなく、プログラムは終了せざる得ません。 このようなとき、WM_CREATEでプログラムに必要なデータの初期化処理を一括管理していれば、 どの処理に失敗した場合も-1を返すという共通の方法で、 プログラムの終了を促すことができるのです。

コードの説明に戻ります。 今回のメッセージボックスは、第4引数がMB_YESNOという定数になっています。

nId = MessageBox(NULL, TEXT("ウインドウの作成を許可しますか"), TEXT("WM_CREATE"), MB_YESNO);

MB_YESNOは、メッセージボックスに[はい]ボタンと[いいえ]ボタンを表示させる機能を持っています。 [はい]ボタンを押したときの戻り値はIDYESとなり、 [いいえ]ボタンを押したときの戻り値はIDNOです。 したがって、IDYESでなければ-1を返すことになります。

if (nId == IDYES)
	return 0;
else
	return -1;

IDYESのときは、ウインドウの作成を許可するわけですから-1を返しません。 このときの戻り値は0になっていますが、それはリファレンスに アプリケーションが処理した場合は0を返すよう書かれていたからです。 C言語が得意の方にとっては、以下のように書いたほうがスマートかもしれません。

return nId == IDYES ? 0 : -1;

今回は、WM_CREATEの他にWM_CLOSEを処理しています。 WM_CLOSEはウインドウの「閉じる」ボタンが押されたときに送られます。 このメッセージが0を返した場合、ウインドウは破棄されません。

case WM_CLOSE: {
	int nId;

	nId = MessageBox(NULL, TEXT("終了してよろしいですか"), TEXT("WM_CLOSE"), MB_YESNO);
	if (nId == IDYES)
		break;
	else
		return 0;
}

nIdがIDYESになるということは、ウインドウを閉じてもよいということです。 このときの処理はbreak、つまりDefWindowProcに処理を任せるわけです。 DefWindowProcは、WM_CLOSEのデフォルトの処理をウインドウの破棄と定めているので、 プログラムがウインドウの破棄に関するコードを書く必要はありません。

今回、紹介した2つのメッセージは非常に実用的であるといえます。 WM_CLOSEは終了確認として使われていますが、 このメッセージが送られているときはプログラムの終了が迫っているので、 データをファイルに保存したり、開放するなどの役割も果たすことができます。 WM_CREATEで-1を返すとウインドウが破棄されるというのは、 非常に重要なので必ず覚えておいてください。


戻る