ウィンドウメッセージ
メッセージを取得する
ウィンドウを生成することはできましたが、ウィンドウを表示しただけですぐに終了してしまいました。一般的なアプリケーションのように、ウィンドウを表示した状態で、ウィンドウサイズを変更したり、ウィンドウを移動させるなどの処理をするには、ウィンドウの動作を処理する反復処理が必要になります。一般に、これをメッセージループと呼んでいます。
Windows は、ウィンドウに対して発生した操作や処理をメッセージと呼ばれるデータで伝えます。ウィンドウの生成や破棄、描画、マウス入力、キー入力、移動やサイズ変更、最大化や最小化など、ウィンドウに対して発生した操作(イベント)はメッセージとしてアプリケーションに伝えられます。アプリケーションはウィンドウに渡されたメッセージを受け取り、メッセージに対応する処理を実行します。メッセージはすべて整数値に対応しており、WM_ から始まる定数と比較して調べられます。
メッセージは、メッセージの種類を表す整数値と、そのパラメータを含む 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() 関数を用います。
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() 関数で基本的な処理を行います。
LRESULT CALLBACK DefWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
DefWindowProc() 関数は標準的なウィンドウの基本動作を提供します。hwnd パラメータにメッセージが送られたウィンドウのハンドル、uMsg パラメータにメッセージ、そして wParam パラメータと lParam パラメータにメッセージの追加情報となる値を指定します。これらの値は MSG 構造体のメンバに対応しています。
#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は GetMessage() 関数で生成したウィンドウに対するメッセージを取り出し、DefWindowProc() 関数にメッセージを渡しています。このプログラムで表示したいウィンドウは、移動やサイズ変更、最大化、最小化などの標準的なウィンドウの操作が可能です。これらの処理は DefWindowProc() 関数によって行われています。
DefWindowProc() 関数はウィンドウに対する閉じるコマンドも処理するため、閉じるボタンを押すと DefWindowProc() 関数によってウィンドウが閉じられます。この時、内部では DestroyWindow() 関数でウィンドウを削除しています。ウィンドウが削除されると WinMain() 関数内の window 変数に保存されているハンドルが無効になるため GetMessage() 関数が失敗するようになります。