EternalWindows
メニュー / メニューの作成

ビジネス系のアプリケーションでは、メニューの存在は必要不可欠といえるでしょう。 メニューは、そのプログラムが行える動作を視覚的に訴え、 マウスから素早くアクセスすることができます。 また、ノベルゲームのようなアプリケーションもメニューを持つことが多いため、 メニューの作成方法はアプリケーションのジャンルを問わず、知っておきたいものです。

ウインドウにメニューを表示するには、まずメニューを作成し、 そのメニューに項目を追加しなくてはなりません。 メニューを作成するには、CreateMenuを呼び出します。

HMENU CreateMenu(VOID);

関数が成功するとメニューのハンドルが返ります。 プログラムでは、メニューをHMENU型で扱うことになります。 この関数が成功した時点では、まだメニューには1つも項目が追加されていません。 項目は、InsertMenuItemで追加することになります。

BOOL InsertMenuItem(
    HMENU hMenu,
    UINT uItem,
    BOOL fByPosition,
    LPCMENUITEMINFO lpmii
);

hMenuは、項目を追加したいメニューのハンドルです。 uItemは、項目の識別子です。 この値により、どの項目が選択されたのかを識別できます。 fByPositionは、uItemの意味を司っています。 FALSEを指定した場合、uItemは項目の識別子として解釈されますが、 それ以外の値を指定すると、uItemは項目の位置として解釈されます。 lpmiiは、MENUITEMINFO構造体のアドレスを指定します。

typedef struct tagMENUITEMINFO {
  UINT    cbSize; 
  UINT    fMask; 
  UINT    fType; 
  UINT    fState; 
  UINT    wID; 
  HMENU   hSubMenu; 
  HBITMAP hbmpChecked; 
  HBITMAP hbmpUnchecked; 
  ULONG_PTR dwItemData; 
  LPTSTR  dwTypeData; 
  UINT    cch; 
  HBITMAP hbmpItem;
} MENUITEMINFO, *LPMENUITEMINFO; 

数多くのメンバがありますが、全てのメンバを一辺に扱うことはありません。 項目の内容や、状態によって使用するメンバが異なるため、 必要に応じて取り上げていくことにします。

メニューに項目を追加したならば、次はメニューをウインドウに割り当てます。 これにより、ウインドウにメニューが表示されるようになります。

BOOL SetMenu(
    HWND hWnd,
    HMENU hMenu
);

hWndは、メニューを割り当てたいウインドウのハンドルを指定します。 hMenuは、メニューのハンドルを指定します。 もし、hMenuにNULLを指定した場合、メニューの割り当ては解除されます。

今回のプログラムは、メニューに2つの項目を追加します。

#include <windows.h>

#define ID_FILE 10
#define ID_HELP 20

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId);
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)
{
	switch (uMsg) {

	case WM_CREATE: {
		HMENU hmenu;

		hmenu = CreateMenu();

		InitializeMenuItem(hmenu, TEXT("ファイル(&F)"), ID_FILE);
		InitializeMenuItem(hmenu, TEXT("ヘルプ"), ID_HELP);
		
		SetMenu(hwnd, hmenu);

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

		if (nId == ID_FILE)
			MessageBox(hwnd, TEXT("ファイルが選択されました。"), TEXT("OK"), MB_OK);
		else if (nId == ID_HELP)
			MessageBox(hwnd, TEXT("ヘルプが選択されました。"), TEXT("OK"), MB_OK);
		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)
{
	MENUITEMINFO mii;
	
	mii.cbSize     = sizeof(MENUITEMINFO);
	mii.fMask      = MIIM_ID | MIIM_TYPE;
	mii.fType      = MFT_STRING;
	mii.wID        = nId;
	mii.dwTypeData = lpszItemName;
	
	InsertMenuItem(hmenu, nId, FALSE, &mii);
}

プログラムを実行すると、ウインドウにメニューが表示され、 「ファイル」と「ヘルプ」という2つの項目が含まれることが分かります。 メニューの選択は主にクリックで行いますが、Altキーとの併用でも可能です。 たとえば、ファイルの横にFという文字がありますが、 これはこの項目が Alt + F というキーの組み合わせでも応答することを意味しています。

メニューの作成と項目の追加は、ウインドウを表示する前に行うことになります。 そうでなければ、ウインドウが表示されたときにメニューを確認することができません。 ウインドウを表示する前の処理は、やはりWM_CREATEが適任でしょう。

case WM_CREATE: {
	HMENU hmenu;

	hmenu = CreateMenu();

	InitializeMenuItem(hmenu, TEXT("ファイル(&F)"), ID_FILE);
	InitializeMenuItem(hmenu, TEXT("ヘルプ"), ID_HELP);
	
	SetMenu(hwnd, hmenu);

	return 0;
}

InitializeMenuItemという自作関数は、内部でInsertMenuItemを呼び出します。 第2引数は項目の名前で、第3引数は項目の識別子です。 ファイル(&F)の(&F)は、この項目が Alt + F のキーの組み合わせに 応答する(WM_COMMANDが発行される)ことを意味しています。 一方、ヘルプには(&H)のような文字列を含めていないため、Altキーには応答しません。 ID_FILEやID_HELPのような識別子は、プログラムの冒頭で定義されています。

#define ID_FILE 10
#define ID_HELP 20

項目の識別子の値には制限が無いので、自由に決めて構いません。 これらの識別子は、項目を選択したときにも参照されることになります。 次に、InitializeMenuItemの内部を見てみましょう。

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId)
{
	MENUITEMINFO mii;
	
	mii.cbSize     = sizeof(MENUITEMINFO);
	mii.fMask      = MIIM_ID | MIIM_TYPE;
	mii.fType      = MFT_STRING;
	mii.wID        = nId;
	mii.dwTypeData = lpszItemName;
	
	InsertMenuItem(hmenu, nId, FALSE, &mii);
}

まず、fMaskメンバに注目してください。 このメンバには、MENUITEMINFO構造体のどのメンバを使うかを指定するメンバです。 項目の追加に必要なメンバは、wIDとfTypeであるため、 MIIM_IDとMIIM_TYPEという定数を論理和で指定します。 wIDには単純に項目の識別子を代入するだけで構いません。 fTypeには項目が文字列なのか、セパレータなのかを示す値を指定します。 セパレータとは、項目の間を挟む細い線のことです。 今回は項目に文字列を指定するのでMFT_STRINGを使います。 そして、dwTypeDataに文字列を指定します。 cbSizeメンバは必ず初期化しなければならないメンバで、 常に構造体のサイズを代入します。 メニューの初期化が終了したら、ウインドウにメニューを割り当てます。

SetMenu(hwnd, hmenu);

これにより、メニューはウインドウに表示されるようになります。 もし、CreateWindowExを呼び出す前にメニューを作成したような場合は、 CreateWindowExの第10引数にメニューのハンドルを指定するとよいでしょう。 このような場合、SetMenuを呼び出す必要はありません。

さて、メニューに項目を追加し、ウインドウに表示するところまでは終了しました。 後は、項目を選択したときの処理を書くだけです。 項目が選択されたとき、その通知はウインドウにWM_COMMANDとして送られます。 このメッセージは、メニュー専用のメッセージではなく、 キーボードのキーを押したときなどでも送られます。

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

	if (nId == ID_FILE)
		MessageBox(hwnd, TEXT("ファイルが選択されました。"), TEXT("OK"), MB_OK);
	else if (nId == ID_HELP)
		MessageBox(hwnd, TEXT("ヘルプが選択されました。"), TEXT("OK"), MB_OK);
	else
		;
	
	return 0;
}

wParamの下位ワードには、項目の識別子が格納されています。 これを、LOWORDマクロで取得し、後は項目に応じた処理をするだけです。 wParamの上位ワード、及びlParamは、メニューの場合は使用されません。

メニューハンドルとCreateWindowEx

ウインドウを作成するCreateWindowExの第10引数には、メニューハンドルを指定できます。 WM_CREATEで初期化を行いたくない場合や、SetMenuによる明示的な割り当てが煩わしい場合は、 WinMain内でメニューを初期化する方法が考えられます。

HMENU hmenu;
・
・
・
hmenu = CreateMenu();

InitializeMenuItem(hmenu, TEXT("ファイル(&F)"), ID_FILE);
InitializeMenuItem(hmenu, TEXT("ヘルプ"), ID_HELP);

hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, hmenu, hinst, NULL);
if (hwnd == NULL)
	return 0;

CreateWindowExでメニューを割り当てている場合でも、SetMenuの第2引数をNULLにすることによって、 メニューの割り当てを解除できます。



戻る