メニューのイベント処理

それではいよいよ、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:

以上、今回はメニューに応答するプログラムを作りました。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説