EternalWindows
ウインドウ管理 / ポストとセンド

メッセージキューにメッセージを格納することは、特別にメッセージのポストと呼ばれ、 システムのみならず、アプリケーション自身も行うことができます。

BOOL PostMessage(
    HWND hWnd,
    UINT Msg,
    WPARAM wParam,
    LPARAM lParam
);

hWndは、メッセージを受け取るウインドウのハンドルを指定します。 Msgは、メッセージキューにポストするメッセージを指定します。 wParamとlParamは、そのメッセージのパラメータを指定します。 関数が成功すると0以外の値が返り、失敗すると0が返ります。 次に、PostMessageを呼び出す例を示します。

case WM_LBUTTONDOWN:
	PostMessage(hwnd, WM_CLOSE, 0, 0);
	return 0;

このコードはWM_CLOSEをポストしていますから、最終的にはWindowProcにWM_CLOSEが 送られることになり、ウインドウはクローズされます。 つまり、マウスの左ボタンの押下がウインドウのクローズとして働いているのです。 このように、メッセージから他のメッセージをポストすれば、 メッセージ間で同じコードを書くようなことを防ぐことができます。

メッセージのポストとは対照的に、メッセージのセンドというものがあります。 メッセージをセンドするには、SendMessaeを呼び出します。

LRESULT SendMessage(
    HWND hWnd,
    UINT Msg,
    WPARAM wParam,
    LPARAM lParam
);

引数は、PostMessaeと同じ意味を持ちます。 戻り値は、メッセージ処理の結果が返ります。 次に、SendMessageの呼び出しの例を示します。

case WM_LBUTTONDOWN:
	SendMessage(hwnd, WM_CLOSE, 0, 0);
	return 0;

メッセージのセンドとは、そのメッセージを直接ウインドウプロシージャに送ることです。 つまり、SendMessageがウインドウプロシージャを呼び出します。 このため、SendMessageが制御を返したときには、 既に引数で指定したメッセージが処理されているということになります。 上記のコードの場合だと、WM_CLOSEが処理されているわけですから、 SendMessageが制御を返したときにはウインドウは破棄されています。 これは、WM_CLOSEのデフォルトの処理がDestroyWindowの呼び出しであり、 DestroyWindowが内部でWM_DESTROYをセンドするからです。 この解釈が正しいかどうかは、次のコードで確認できます。

case WM_LBUTTONDOWN:
	SendMessage(hwnd, WM_CLOSE, 0, 0);
	if (IsWindow(hwnd)) {
		...
	}
	return 0;

IsWindowは、指定されたウインドウハンドルを持つウインドウが存在するかを調べ、 存在する場合は0以外を、存在しない場合は0を返します。 上記コードの場合、返される値は0になります。 つまり、SendMessageが制御を返したときにはメッセージが処理されており、 WM_CLOSEはDestroyWindowを呼び出しているということになります。 逆に、SendMessageではなくPostMessageを呼ぶようにした場合は、 IsWindowは0以外の値を返します。 PostMessageは、メッセージをメッセージキューに格納するだけですから、 この関数が制御を返した時点ではメッセージは処理されていません。 つまり、まだWM_CLOSEは処理されていないので、ウインドウは破棄されておらず、 IsWindowはウインドウが存在するものと判断するのです。

今回のプログラムは、WM_SYSCOMMANDというメッセージを明示的にポストすることで、 システムコマンドの押下を再現しています。

#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_LBUTTONDOWN:
		if (IsZoomed(hwnd))
			PostMessage(hwnd, WM_SYSCOMMAND, SC_RESTORE, 0);
		else
			PostMessage(hwnd, WM_SYSCOMMAND, SC_MAXIMIZE, 0);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

システムコマンドとは、システムメニューに存在する各項目のことを意味します。 これらの項目を選択すると、wParamにその項目の動作を表す定数を格納したWM_SYSCOMMANDが送られることになっているため、 メッセージのポストやセンドで項目の動作を任意のタイミングで引き出すことができます。

case WM_LBUTTONDOWN:
	if (IsZoomed(hwnd))
		PostMessage(hwnd, WM_SYSCOMMAND, SC_RESTORE, 0);
	else
		PostMessage(hwnd, WM_SYSCOMMAND, SC_MAXIMIZE, 0);
	return 0;

この処理では、左ボタンの押下をウインドウの最大化に見立てようとしています。 IsZoomedは、ウインドウが最大化をしていると0以外の値を返す関数で、 0が返った場合は最大化していないことを意味しています。 このとき、SC_MAXIMIZEをwParamとしてWM_SYSCOMMANDをポストすることで、 ウインドウは最大化されることになります。 既にウインドウが最大化されているときは、SC_RESTOREを指定することによって、 ウインドウを元のサイズに戻します。

先に述べたように、一般にシステムコマンドといえば、 システムメニューに存在する各項目のことを意味しますが、 WM_SYSCOMMANDが定義するシステムコマンドはもう少し広義に渡ります。 次にWM_SYSCOMMANDのwParamに指定できる定数の一部を示します。

フラグ 意味
SC_CLOSE ウインドウを閉じる
SC_MAXIMIZE ウインドウを最大化する
SC_MINIMIZE ウインドウを最小化する
SC_RESTORE ウインドウのサイズを元に戻す
SC_SCREENSAVE スクリーンセーバーを起動する
SC_TASKLIST スタートメニューを表示する

このように、スクリーンセーバーやスタートメニューを起動できるというのは、 WM_SYSCOMMANDの大きな魅力といえるでしょう。 しかし、WM_SYSCOMMANDがこれらすべてのシステムコマンドを処理するとは限りません。 このメッセージの本来の位置づけはその動作の許否を決定することであり、 処理をDefWindowProcに任せることで、後続のメッセージが内部で送られることになっています。 たとえば、SC_CLOSEを指定したWM_SYSCOMMANDは内部でWM_CLOSEをセンドしますが、 アプリケーションがSC_CLOSEに対して0を返した場合はこれが行われないため、 ウインドウはクローズされないことになります。


戻る