EternalWindows
メニュー / メニューの状態

メニューの項目は、そのアプリケーションの状況も表しています。 たとえば、何らかの時間の掛かる処理を行うような場合、項目を一時的に無効にする ことによって、項目の選択を回避するアプリケーションはよくあります。 また、アプリケーション内で何らかの機能が有効になっている場合、 その機能を表す項目にチェックが付くこともあったりします。 このようなメニューの状態に関わる事柄は、SetMenuItemInfoで扱います。

BOOL SetMenuItemInfo(
    HMENU hMenu,
    UINT uItem,
    BOOL fByPosition,
    LPMENUITEMINFO lpmii
);

この関数の引数の意味はInsertMenuItemInfoと全く同じであり、 InsertMenuItemInfoと同じ要領で使いこなすことができます。 ただし、この関数を呼び出すのは多くの場合、WM_CREATEではなく その他のメッセージになるでしょうから、メニューハンドル静的に宣言して、 どこからでも参照できるようにしておかなければならないように思えます。 勿論、そのような方法もあるのですが、メニューはウインドウに関連付けられていることから、 ウインドウハンドルさえあればGetMenuでいつでも取得することが可能になっています。

HMENU GetMenu(
  HWND hWnd
);

hWndは、メニューを取得したいウインドウのハンドルを指定します。 戻り値は、メニューのハンドルとなります。

GetMenuは、ウインドウに関連付けられているメニューを取得する関数であり、 ポップアップメニューを取得することはできません。 しかし、ポップアップメニューは親のメニューに関連付けられていますから、 親のハンドルをGetSubMenuに指定すれば、ポップアップメニューを取得できます。

HMENU GetSubMenu(
  HMENU hMenu,
  int nPos
);

hMenuは、親メニューのハンドルを指定します。 nPosは、ゼロベースで始まる項目の位置を指定します。 たとえば、0を指定すると最初のポップアップメニューが取得対象となります。 戻り値は、ポップアップメニューのハンドルとなります。

今回のプログラムは、ポップアップメニューに「終了拒否」という項目を設定し、 それが選択されたかどうかで項目の状態が変化します。

#include <windows.h>

#define ID_FILE  10
#define ID_DENY  20
#define ID_EXIT  30

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId, HMENU hmenuSub);
void ChangeMenuItem(HMENU hmenu, int nId, ULONG uState);
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)
{
	static BOOL bDeny = FALSE;

	switch (uMsg) {

	case WM_CREATE: {
		HMENU hmenu;
		HMENU hmenuPopup;

		hmenu = CreateMenu();
		hmenuPopup = CreatePopupMenu();
			
		InitializeMenuItem(hmenuPopup, TEXT("終了拒否(&D)"), ID_DENY, NULL);
		InitializeMenuItem(hmenuPopup, NULL, 0, NULL);
		InitializeMenuItem(hmenuPopup, TEXT("終了(&X)"), ID_EXIT, NULL);

		InitializeMenuItem(hmenu, TEXT("ファイル(&F)"), ID_FILE, hmenuPopup);

		SetMenu(hwnd, hmenu);

		return 0;
	}

	case WM_COMMAND: {
		int nId = LOWORD(wParam);

		if (nId == ID_DENY) {
			HMENU hmenuPopup;

			bDeny = !bDeny;
			
			hmenuPopup = GetSubMenu(GetMenu(hwnd), 0);

			ChangeMenuItem(hmenuPopup, ID_DENY, bDeny ? MFS_CHECKED : MFS_UNCHECKED);
			ChangeMenuItem(hmenuPopup, ID_EXIT, bDeny ? MFS_GRAYED : MFS_ENABLED);
		}
		else if (nId == ID_EXIT)
			PostMessage(hwnd, WM_CLOSE, 0, 0);
		else
			;
		
		return 0;
	}

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId, HMENU hmenuSub)
{
	MENUITEMINFO mii;
	
	mii.cbSize = sizeof(MENUITEMINFO);
	mii.fMask  = MIIM_ID | MIIM_TYPE;
	mii.wID    = nId;

	if (lpszItemName != NULL) {
		mii.fType      = MFT_STRING;
		mii.dwTypeData = lpszItemName;
	}
	else
		mii.fType = MFT_SEPARATOR;

	if (hmenuSub != NULL) {
		mii.fMask   |= MIIM_SUBMENU;
		mii.hSubMenu = hmenuSub;
	}

	InsertMenuItem(hmenu, nId, FALSE, &mii);
}

void ChangeMenuItem(HMENU hmenu, int nId, ULONG uState)
{
	MENUITEMINFO mii;
	
	mii.cbSize = sizeof(MENUITEMINFO);
	mii.fMask  = MIIM_ID | MIIM_STATE;
	mii.fState = uState;
	mii.wID    = nId;

	SetMenuItemInfo(hmenu, nId, FALSE, &mii);
}

まず、WM_COMMANDのコードに注目します。

if (nId == ID_DENY) {
	HMENU hmenuPopup;

	bDeny = !bDeny;
			
	hmenuPopup = GetSubMenu(GetMenu(hwnd), 0);

	ChangeMenuItem(hmenuPopup, ID_DENY, bDeny ? MFS_CHECKED : MFS_UNCHECKED);
	ChangeMenuItem(hmenuPopup, ID_EXIT, bDeny ? MFS_GRAYED : MFS_ENABLED);
}

ID_DENYは、「終了拒否」という項目の識別子です。 bDenyは静的変数で、TRUEのときは終了の拒否を意味しています。 ChangeMenuItemは、内部でSetMenuItemInfoを呼び出しています。 第2引数は、変更対象となる項目の識別子、第3引数は設定する状態です。 MFS_CHECKEDはチェックを付け、MFS_UNCHECKEDはチェックを外します。 MFS_GRAYEDは項目を使用不可にし、MFS_ENABLEDは使用可能にします。 これらは、MENUITEMINFO構造体のfStateメンバに設定されます。

void ChangeMenuItem(HMENU hmenu, int nId, ULONG uState)
{
	MENUITEMINFO mii;
	
	mii.cbSize = sizeof(MENUITEMINFO);
	mii.fMask  = MIIM_ID | MIIM_STATE;
	mii.fState = uState;
	mii.wID    = nId;

	SetMenuItemInfo(hmenu, nId, FALSE, &mii);
}

構造体と関数の使い方は、InsertMenuItemと似ているので特に説明は不要でしょう。 fStateメンバを使用するときは、fMaskメンバにはMIIM_STATEを加えます。

状態変更の局所化

メニュー項目の状態を変えるべきタイミングというのは、 プログラムによっては非常に多くのケースが考えられ、 その時々に応じて変更を行うコードを記述するのは大変な作業です。 そもそも、項目の状態を変更すべきときというのは、実際にメニューを表示する前で あればよいはずですから、変更内容のみを変数に保存しておき、 メニューの表示段階で変数を確認して状態を変更したほうが、 管理のしやすいコードになるはずです。 ポップアップメニューが表示されようとしているときには WM_INITMENUPOPUPが送られるため、これを利用したコードを次に示します。

case WM_INITMENUPOPUP: {
	HMENU hmenuPopup;
	
	hmenuPopup = (HMENU)wParam;

	ChangeMenuItem(hmenuPopup, ID_DENY, bDeny ? MFS_CHECKED : MFS_UNCHECKED);
	ChangeMenuItem(hmenuPopup, ID_EXIT, bDeny ? MFS_GRAYED : MFS_ENABLED);

	return 0;
}

case WM_COMMAND: {
	int nId = LOWORD(wParam);

	if (nId == ID_DENY)
		bDeny = !bDeny;
	else if (nId == ID_EXIT)
		PostMessage(hwnd, WM_CLOSE, 0, 0);
	else
		;
	
	return 0;
}

このコードでは、WM_INITMENUPOPUPでbDenyの値を確認して状態を変更するため、 WM_COMMANDでChangeMenuItemを呼び出していないことに注意してください。 この設計方針の場合、状態を変更すべきタイミングの数だけbDenyのような変数が 必要となりますが、状態変更のコードを一箇所に局所化できるという点を 踏まえると、WM_INITMENUPOPUPの方が扱いやすいと思われます。 WM_INITMENUPOPUPのwParamは、表示されようとしているポップアップメニューのハンドルです。



戻る