メニューのイベント処理
それではいよいよ、Windows のプログラムらしく、メニューに応答してみましょう。 出来上がりはこのようになります。
図1. メニューを選択するとポップアップが表示される
ではさっそく、いつも通り、先にコードを書いてビルドしてみましょう。
前回までの simple.cpp を以下のように変更します。変更箇所は赤字で示しています。
#include <windows.h> #include "resource.h" #define WND_CLASS_NAME TEXT("My_Window") LRESULT CALLBACK WindowProc( HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam); HWND g_hWnd; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { HWND hWnd; WNDCLASSEX wcl; wcl.cbSize = sizeof(WNDCLASSEX); wcl.hInstance = hInstance; wcl.lpszClassName = WND_CLASS_NAME; wcl.lpfnWndProc = WindowProc; wcl.style = NULL; wcl.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APP)); wcl.hIconSm = NULL; wcl.hCursor = LoadCursor(NULL, IDC_ARROW); wcl.lpszMenuName = MAKEINTRESOURCE (IDR_MENU_MAIN); wcl.cbClsExtra = 0; wcl.cbWndExtra = 0; wcl.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); if(!RegisterClassEx(&wcl)) { return FALSE; } hWnd = CreateWindowEx( NULL, WND_CLASS_NAME, TEXT("Windows Programming Primer - Simple"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); if(!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); MSG msg; BOOL bRet; while( ( bRet = GetMessage( &msg, hWnd, 0, 0 ) ) != 0) { if (bRet == -1) { break; } else { DispatchMessage( &msg ); } } return msg.wParam; } LRESULT CALLBACK WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { int id; HWND hwndCtl; UINT codeNotify; PAINTSTRUCT ps; HDC hdc; RECT rect; switch( uMsg ) { case WM_COMMAND: id = (int) LOWORD(wParam); codeNotify = (UINT) HIWORD(wParam); hwndCtl = (HWND) lParam; switch( id ) { case IDM_HELP_ABOUT: ::MessageBox ( NULL, TEXT("Hello, About!"), TEXT("About This Program"), MB_OK ); break; } break; case WM_PAINT: hdc = BeginPaint ( hwnd, &ps ); GetClientRect ( hwnd, &rect ); DrawText( hdc, TEXT("Hello, world!"), -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE ); EndPaint( hwnd, &ps ); break; case WM_SIZE: InvalidateRect( hwnd, NULL, TRUE ); break; case WM_DESTROY: ::PostQuitMessage( 0 ); break; default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0; }
リソースファイル (resource.rc)、 リソースヘッダファイル (resource.h)、メイクファイル (makefile) は前回と全く同様です。
必要なファイル (simple.cpp, resource.rc, resource.h, app.ico, makefile) をひとつのディレクトリに保存し、 Visual Studio コマンドプロンプトから nmake してください。これで実行可能なプログラムがビルドできるはずです。
> nmake -a
Microsoft (R) Program Maintenance Utility Version 9.00.30729.01
Copyright (C) Microsoft Corporation. All rights reserved.
if not exist ".\chk/" mkdir ".\chk"
cl /nologo /MT /W3 /Fo".\chk\\" /Fd".\chk\\" /c /Zi /DWIN32 /DUNICODE /D_UNICODE "simple.cpp"
simple.cpp
rc /l 0x411 /fo".\chk\resource.res" /d DEBUG "resource.rc"
Microsoft (R) Windows (R) Resource Compiler Version 6.1.6723.1
Copyright (C) Microsoft Corporation. All rights reserved.
link.exe user32.lib gdi32.lib /nologo /subsystem:windows /pdb:".\chk\simple.pdb"
/machine:I386 /out:".\chk\simple.exe" /DEBUG /RELEASE ".\chk\simple.obj"
".\chk\resource.res"
chk ディレクトリに出来た simple.exe を実行してみてください。上記のスクリーンショットのように、 ヘルプメニューが動くようになっているでしょうか。
メニューのハンドラを書く
それでは、メニューを選択したときにポップアップを出すために書いたコードの意味を説明します。
メニューを選択したときには、WM_COMMAND メッセージが発生します。 ですから、WindowProc に、WM_COMMAND メッセージに対するハンドラを追加することで、 メニューを選択したときの処理を行うことが出来ます。
switch( uMsg ) {
case WM_COMMAND:
id = (int) LOWORD(wParam);
codeNotify = (UINT) HIWORD(wParam);
hwndCtl = (HWND) lParam;
switch( id ) {
case IDM_HELP_ABOUT:
::MessageBox (
NULL,
TEXT("Hello, About!"),
TEXT("About This Program"),
MB_OK );
break;
}
break;
case WM_PAINT:
ちなみに、WM_COMMAND というメッセージはメニュー固有のものではなく、ボタンなどのコントロールの操作によっても発生します。 WM_COMMAND の発生源と、渡される値の関係は次のようになります。
メッセージソース | wParam (上位ワード) | wParam (下位ワード) | lParam |
メニュー | 0 | メニュー識別子 | 0 |
アクセラレータ | 1 | アクセラレータ識別子 | 0 |
コントロール | コントロール別に定義された通知コード | コントロール識別子 | コントロールウィンドウのハンドル |
図2. WM_COMMAND 処理のときの wParam と lParam 値の内容
今回は「メニュー」の行に着目してください。
どのメニュー項目が選択されたのかは、WindowProc に渡される wParam からわかります。 「wParam の下位ワードに、メニューの識別子が渡される」のです。
「wParam の下位ワードに、メニューの識別子が渡される」 とはどういうことでしょうか。 wParam は WPARAM 型です。ウィンドウのあるプログラム作成 ~ Hello, world 以前 で触れたように、WPARAM, LPARAM は共に UINT_PTR で、実質的に 32 ビット環境では、4バイトのデータ でしたね。 また 1 ワード (Word) は 2 バイトです。すなわち、WPARAM を構成する 4 バイトのうち、下位の 2 バイトに、 メニューの識別子が渡されるということです。下位というのはアドレスの小さいほうということです。
wParam の下位ワードを取り出すのは簡単です。LOWORD というマクロを使って取り出せます。
switch( uMsg ) {
case WM_COMMAND:
id = (int) LOWORD(wParam);
codeNotify = (UINT) HIWORD(wParam);
hwndCtl = (HWND) lParam;
switch( id ) {
case IDM_HELP_ABOUT:
::MessageBox (
NULL,
TEXT("Hello, About!"),
TEXT("About This Program"),
MB_OK );
break;
}
break;
case WM_PAINT:
マクロの意味をはっきりさせるために、簡単な実験をしてみました。
>>MAKEWPARAM, HIWORD, LOWORD マクロとは?
メニュー識別子は、リソースファイルに定義した値です。覚えていますか?おさらいしておきましょう。
リソースファイルに書いたメニューの定義部分は以下です。
IDR_MENU_MAIN MENU BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "開く(&O)...", IDM_FILE_OPEN MENUITEM SEPARATOR MENUITEM "終了(&X)", IDM_FILE_EXIT END POPUP "ヘルプ(&H)" BEGIN MENUITEM "バージョン情報(&A)...", IDM_HELP_ABOUT END END
ここで赤でマークした箇所は、resource.h ヘッダファイルで定義されている定数です。
// Menu Item ID #define IDM_FILE_OPEN 2000 #define IDM_FILE_EXIT 2001 #define IDM_HELP_ABOUT 2100
wParam の上位ワードには通知コード (Notification Code) が渡されます。 ここでは、一応 codeNotify という変数に取り出していますが、使っていません。
lParam にはコントロールのウィンドウハンドルが渡されます。(しかしメニューの場合は常に 0 になります)
以上をまとめると、
「メニューをクリックすると、WM_COMMAND が送られる。
wParam の下位ワードからメニューの識別子が渡される。」
ということです。
従って、「ヘルプ」メニューの「バージョン情報」に割り当てた識別子は IDM_HELP_ABOUT (=2100) ですから、 以下の箇所でこのメッセージを処理すればよいことになります。
switch( uMsg ) {
case WM_COMMAND:
id = (int) LOWORD(wParam);
codeNotify = (UINT) HIWORD(wParam);
hwndCtl = (HWND) lParam;
switch( id ) {
case IDM_HELP_ABOUT:
::MessageBox (
NULL,
TEXT("Hello, About!"),
TEXT("About This Program"),
MB_OK );
break;
}
break;
case WM_PAINT:
以上、今回はメニューに応答するプログラムを作りました。