WisdomSoft - for your serial experiences.

ウィンドウメッセージ

ウィンドウに対する操作や要求はメッセージと呼ばれる整数によって知らされます。アプリケーションはメッセージを取得し、対象のウィンドウを制御します。

メッセージを取得する

ウィンドウを生成することはできましたが、ウィンドウを表示しただけですぐに終了してしまいました。一般的なアプリケーションのように、ウィンドウを表示した状態で、ウィンドウサイズを変更したり、ウィンドウを移動させるなどの処理をするには、ウィンドウの動作を処理する反復処理が必要になります。一般に、これをメッセージループと呼んでいます。

Windows は、ウィンドウに対して発生した操作や処理をメッセージと呼ばれるデータで伝えます。ウィンドウの生成や破棄、描画、マウス入力、キー入力、移動やサイズ変更、最大化や最小化など、ウィンドウに対して発生した操作(イベント)はメッセージとしてアプリケーションに伝えられます。アプリケーションはウィンドウに渡されたメッセージを受け取り、メッセージに対応する処理を実行します。メッセージはすべて整数値に対応しており、WM_ から始まる定数と比較して調べられます。

メッセージは、メッセージの種類を表す整数値と、そのパラメータを含む MSG 構造体で表現されています。

MSG 構造体
typedef struct tagMSG {
    HWND   hwnd;     
    UINT   message; 
    WPARAM wParam; 
    LPARAM lParam; 
    DWORD  time; 
    POINT  pt; 
} MSG;

hwnd メンバは、このメッセージを受け取ったウィンドウを表します。メッセージは message メンバに格納され、そのパラメータが wParam メンバと lParam メンバに格納されます。WPARAM 型と LPARAM 型は単純な整数型ですが、情報が多い場合は構造体へのポインタをパラメータとすることもあります。wParam メンバと lParam メンバに格納される値の意味は、メッセージによって異なります。例えば、キーボードやマウスの入力イベントを通知するメッセージであれば押されたボタンを表す値が格納されます。

time メンバはメッセージが発行された時間を表し、pt メンバはメッセージが発行された時のカーソル位置を表します。

ウィンドウは、送信された処理待ちのメッセージを順に並べているキューを保持しています。アプリケーションはウィンドウからメッセージを取り出し、メッセージに応じた処理をしなければなりません。メッセージを取得するには GetMessage() 関数を用います。

GetMessage() メッセージ
BOOL GetMessage(
  LPMSG lpMsg, HWND hWnd,
  UINT wMsgFilterMin, UINT wMsgFilterMax
);

lpMsg パラメータにメッセージを受け取る MSG 構造体へのポインタを指定します。ウィンドウがメッセージを受け取ると、このパラメータに渡した MSG 構造体にメッセージが格納されます。

hWnd パラメータはメッセージを取り出すウィンドウのハンドルを指定します。ウィンドウを特定せず、このアプリケーションに関連するメッセージを受け取る場合は hWnd パラメータに NULL を指定してください。

wMsgFilterMin パラメータと wMsgFilterMax パラメータには、取得するメッセージを特定の範囲に限定したい場合に利用します。通常は、すべてのメッセージを処理する必要があるので、これらの引数には 0 を指定します。範囲を指定する場合は wMsgFilterMin にメッセージの最小値を、wMsgFilterMax パラメータに最大値を指定します。

GetMessage() 関数の戻り値は、成功すれば 0 より大きい値が、アプリケーションを終了させるメッセージを表す WM_QUIT メッセージが発生すれば 0 を返します。無効なハンドルを渡すなどの理由でエラーが発生した場合は -1 を返します。通常、メッセージを取り出す時に GetMessage() 関数の戻り値を調べ、0 が返された場合はアプリケーションを終了させます。

メッセージループでは、常に GetMessage() 関数を呼び出してメッセージが発生するのを待ちます。ウィンドウに何らかのアクションが発生すれば、GetMessage() 関数からメッセージを取得し、そのメッセージに基づいて必要な処理に導きます。この繰り返しが、ウィンドウアプリケーションを成り立たせています。

GetMessage() 関数は処理するメッセージが存在しない場合は、新しいメッセージを受け取るまで制御を返しません。メッセージループでは GetMessage() を呼び出し、ウィンドウのキューにメッセージが追加されるまで待てばよいのです。アプリケーションは GetMessage() 関数で新しいメッセージが送られてくるのを待機し、メッセージを受け取ると値を調べて、対応する処理を実行します。

メッセージの取得と処理を行う流れは、例えば、以下のような形になります。

while(TRUE)
{
	MSG msg;
	int r = GetMessage(&msg, NULL, 0, 0);
	if (r > 0) { /* メッセージ処理 */ }
	else if (r == -1) { /* エラー処理 */ }
	else { /* 終了処理 */ break; }
}

アプリケーションはウィンドウが操作されるのを GetMessage() 関数で待機し、メッセージを受信すると制御が返されるので、メッセージに対応した処理を実行します。このように、デスクトップアプリケーションは常にメッセージループ内でメッセージの受信と処理を繰り返すことでウィンドウを維持します。

メッセージ処理

ウィンドウは、受け取ったメッセージに対応する処理を実行しなければ正常に動作しません。例えば、ウィンドウの移動やサイズの変更も、メッセージで伝えらたマウスやキーボードの入力イベントを処理することで実現されています。

すべてのメッセージを自分で処理することもできますが、Windows の標準的なウィンドウ操作を実現するだけでも多くのメッセージ処理が必要なので、それをアプリケーションごとに自前で記述するのは現実的ではありません。通常は Windows API で提供されている DefWindowProc() 関数で基本的な処理を行います。

DefWindowProc() 関数
LRESULT CALLBACK DefWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

DefWindowProc() 関数は標準的なウィンドウの基本動作を提供します。hwnd パラメータにメッセージが送られたウィンドウのハンドル、uMsg パラメータにメッセージ、そして wParam パラメータと lParam パラメータにメッセージの追加情報となる値を指定します。これらの値は MSG 構造体のメンバに対応しています。

コード1
#include <windows.h>
#define WINDOWS_CLASS_NAME TEXT("WisdomSoft.Sample.Window")

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HWND window;
	WNDCLASSEX wcx;

	wcx.cbSize = sizeof(WNDCLASSEX);
	wcx.style = CS_HREDRAW | CS_VREDRAW;
	wcx.lpfnWndProc = DefWindowProc;
	wcx.cbClsExtra = 0;
	wcx.cbWndExtra = 0;
	wcx.hInstance = hInstance;
	wcx.hIcon =  NULL;
	wcx.hCursor = NULL;
	wcx.hbrBackground = (HBRUSH)COLOR_BACKGROUND + 1;
	wcx.lpszMenuName = NULL;
	wcx.lpszClassName = WINDOWS_CLASS_NAME;
	wcx.hIconSm = NULL;

	if (!RegisterClassEx(&wcx)) 
	{
		OutputDebugString(TEXT("Error: ウィンドウクラスの登録ができません。\n"));
		return 0;
	}

	window = CreateWindowEx(
		WS_EX_LEFT, WINDOWS_CLASS_NAME, TEXT("Window Title"),
		WS_OVERLAPPEDWINDOW | WS_VISIBLE,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
		NULL, NULL, hInstance ,NULL
	);
	if (!window)
	{
		OutputDebugString(TEXT("Error: ウィンドウが作成できません。\n"));
		return 0;
	}
	
	while(TRUE)
	{
		MSG msg;
		int r = GetMessage(&msg, window, 0, 0);
		if (r > 0) DefWindowProc(window, msg.message, msg.wParam, msg.lParam);
		else if(r == -1)
		{
			OutputDebugString(TEXT("Error: メッセージの取得に失敗しました。\n"));
			break;
		}
		else
		{
			break;
		}
	}
	return 0;
}
実行結果
コード1 実行結果

コード1は GetMessage() 関数で生成したウィンドウに対するメッセージを取り出し、DefWindowProc() 関数にメッセージを渡しています。このプログラムで表示したいウィンドウは、移動やサイズ変更、最大化、最小化などの標準的なウィンドウの操作が可能です。これらの処理は DefWindowProc() 関数によって行われています。

DefWindowProc() 関数はウィンドウに対する閉じるコマンドも処理するため、閉じるボタンを押すと DefWindowProc() 関数によってウィンドウが閉じられます。この時、内部では DestroyWindow() 関数でウィンドウを削除しています。ウィンドウが削除されると WinMain() 関数内の window 変数に保存されているハンドルが無効になるため GetMessage() 関数が失敗するようになります。