メニューバー

ウィンドウはメニューを持つことができます。
メニューを使用すると、ユーザーの任意のタイミングで任意の処理を呼び出すことができます。

メニューの作成はリソーススクリプト(リソースファイル)を使用する方法と、関数を使用して作成する方法があります。
まずはリソースファイルを使用する方法を説明します。

メニューバーの作成

リソースファイルの項を参考にして、まずはリソーススクリプトを作成します。
(すでにある場合はスキップ)
リソースの追加ダイアログを開き、「Menu」を選択して「新規作成」を押下します。
メニューの新規作成

すると以下のようなメニュー作成用のエディタが開かれます。
メニューエディタを開いた状態

「ここに入力」と表示されている箇所をクリックし、任意の文字を入力します。
このとき現在入力している箇所の下と右にも新しく「ここに入力」の項目が表示されますので、必要な項目をどんどん追加していきます。
視覚的にわかりやすいので戸惑うことはないでしょう。
メニュー項目の追加

ここではとりあえず「ファイル」メニュー内に「保存」と「終了」の項目を作成しています。
上図では「保存」と「終了」の項目の間にセパレーター(しきり)を挿入していますが、これはメニューの文字列にハイフン(-)だけを入力することで作成できます。

なお、メニューバーの各項目を選択したときに表示されるドロップダウン形式のメニューをサブメニューといいます。
(今回は「保存」と「終了」の項目が表示されているやつがサブメニュー)

ここまで入力したらいったん保存します。
(ファイルメニューから。もしくは「Ctrl + S」)
メニューエディタを閉じ、リソーススクリプト(Resource.rc)を開くと以下のような「Menu」セクションが追加されています。


/////////////////////////////////////////////////////////////////////////////
//
// Menu
//

IDR_MENU1 MENU
BEGIN
    POPUP "ファイル"
    BEGIN
        MENUITEM "保存",                          ID_40001
        MENUITEM SEPARATOR
        MENUITEM "終了",                          ID_40002
    END
END

今入力したメニュー項目はこのようなリソーススクリプトで表現されているわけです。
「保存」と「終了」にはID(識別子)が割り振られており、これを利用してコード上からクリック等の判定処理を行います。
メニュー自体にも「IDR_MENU1」という識別子が割り振られているので、これを利用してウィンドウ上にメニューを配置します。

これらの識別子の定義は「resource.h」ファイルにも自動的に追加されます。
識別子を手動で変更する場合はこのヘッダファイル内の定義も変更する必要があります。

メニューエディタを閉じた後、再度開くにはソリュージョンエクスプローラーから「リソースファイル」→「Resource.rc」をダブルクリックし「リソースビュー」を表示します。
「Resource.rc」→「Menu」を展開し、「IDR_MENU1」をダブルクリックすることでメニューエディタが開きます。
メニューエディタの再表示

×マークが表示されていて開けない場合はリソーススクリプト(Resource.rc)およびリソースヘッダファイル(resource.h)を閉じてください。

メニューの表示

先ほど作成したメニューをウィンドウ上に表示します。
これにはいくつか方法があります。

ウィンドウクラスに登録

ウィンドウクラスの作成と登録のためにWNDCLASS構造体(WNDCLASSEX構造体)を使用しましたが、これにはlpszMenuNameというメンバがあります。
(→ウィンドウクラスを登録するを参照)
ここに先ほどのメニューの識別子(IDR_MENU1)をMAKEINTRESOURCEマクロを使用して指定します。


#include <windows.h>

//リソースヘッダファイルのインクルード
#include "resource.h"

//ウィンドウクラスの登録
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;
	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = WndProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wcex.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1); //メニューの登録
	wcex.lpszClassName = szWindowClass;
	wcex.hIconSm = NULL;

	return RegisterClassEx(&wcex);
}

MAKEINTRESOURCEマクロを使用する理由についてはリソースを指定する識別子および文字列についてを参照してください。

ビルトするとウィンドウにメニューが表示されるようになります。
ただし、まだメニューの動作を何も定義していないので項目をクリックしても何も起こりません。
メニューをウィンドウ上に表示

CreateWindow関数の引数に指定

CreateWindow関数の第九引数(親ウィンドウハンドルの次、インスタンスハンドルの前)にメニューハンドルを指定することで、メニューを表示することができます。


#include <windows.h>

//リソースヘッダファイルのインクルード
#include "resource.h"

//ウィンドウの作成
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
	hInst = hInstance;

	HWND hWnd = CreateWindow(
		szWindowClass, szTitle,
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT,
		NULL,
		LoadMenu(hInst, MAKEINTRESOURCE(IDR_MENU1)), //メニューハンドルの指定
		hInstance,
		NULL
	);

	if (!hWnd) {
		return FALSE;
	}
	ShowWindow(hWnd, nCmdShow);

	return TRUE;
}

ここに指定するのはメニューハンドルです。
メニューハンドルはLoadMenu関数で取得できます。

HMENU LoadMenuW(
 HINSTANCE hInstance,
 LPCWSTR lpMenuName
);
インスタンスhInstanceからメニューlpMenuNameを読み込む。
戻り値はメニューハンドル。

ウィンドウクラスに登録したメニューよりもCreateWindow関数で指定したメニューのほうが優先して表示されます。

SetMenu関数

ウィンドウクラスやCreateWindow関数でのメニュー表示は、最初に指定したものがそのまま表示されますが(静的)、状態にあわせてメニューを切り替えたい場合(動的)もあります。
SetMenu関数を使用すれば任意のタイミングでメニューの表示/非表示/切り替えができます。

BOOL SetMenu(
 HWND hWnd,
 HMENU hMenu
);
ウィンドウhWndにメニューハンドルhMenuを割り当てる。
hMenuがNULLの場合はメニューを削除する。
成功した場合は0以外を、失敗した場合は0を返す。

以下のコードは先ほど作成したメニュー(IDR_MENU1)に加えて、「メニュー2」という項目のみを持ったメニュー(IDR_MENU2)を作成し、切り替えるサンプルです。
(SetMenu関数の説明のためだけのメニューなので内容は適当で良いです)


/////////////////////////////////////////////////////////////////////////////
//
// Menu
//

IDR_MENU1 MENU
BEGIN
    POPUP "ファイル"
    BEGIN
        MENUITEM "保存",                          ID_40001
        MENUITEM SEPARATOR
        MENUITEM "終了",                          ID_40002
    END
END

IDR_MENU2 MENU
BEGIN
    MENUITEM "メニュー2",                       0
END

#include <windows.h>
#include "resource.h"

//ウィンドウの生成等は省略

#define IDC_BUTTON_NOMENU	100
#define IDC_BUTTON_MENU1	101
#define IDC_BUTTON_MENU2	102

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HMENU hMenu1, hMenu2;
	switch (message)
	{
	case WM_CREATE:
		hMenu1 = LoadMenu(hInst, MAKEINTRESOURCE(IDR_MENU1));
		hMenu2 = LoadMenu(hInst, MAKEINTRESOURCE(IDR_MENU2));

		CreateWindow(
			L"BUTTON", L"メニューなし",
			WS_CHILD | WS_VISIBLE,
			0, 0, 120, 25,
			hWnd, (HMENU)IDC_BUTTON_NOMENU, hInst, NULL);

		CreateWindow(
			L"BUTTON", L"メニュー1",
			WS_CHILD | WS_VISIBLE,
			0, 25, 120, 25,
			hWnd, (HMENU)IDC_BUTTON_MENU1, hInst, NULL);

		CreateWindow(
			L"BUTTON", L"メニュー2",
			WS_CHILD | WS_VISIBLE,
			0, 50, 120, 25,
			hWnd, (HMENU)IDC_BUTTON_MENU2, hInst, NULL);
		break;

	case WM_COMMAND: //コントロールの操作
		switch (LOWORD(wParam))
		{
		case IDC_BUTTON_NOMENU: //メニューなし
			SetMenu(hWnd, NULL);
			break;

		case IDC_BUTTON_MENU1: //メニュー1
			SetMenu(hWnd, hMenu1);
			break;

		case IDC_BUTTON_MENU2: //メニュー2
			SetMenu(hWnd, hMenu2);
			break;
		}
		break;

	case WM_DESTROY: //ウィンドウの破棄
		DestroyMenu(hMenu1);
		DestroyMenu(hMenu2);
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

それぞれのボタンを押すことでメニューの切り替えや削除ができます。
メニューを動的に切り替える

DestroyMenu関数

DestroyMenu関数はLoadMenu関数でロードしたメニューを破棄します。

BOOL DestroyMenu(
 HMENU hMenu
);
メニューハンドルhMenuを破棄し、メモリを解放する。
成功した場合は0以外を、失敗した場合は0を返す。

ウィンドウに割り当てられているメニューは、プログラムの終了時に自動的に破棄されます。
それ以外のメニューは破棄されないので、明示的に破棄する必要があります。
上記のサンプルコードではWM_DESTROYメッセージ内でメニューを破棄しています。

クリック時の処理

メニュー項目をクリックすると、親ウィンドウにWM_COMMANDメッセージが送信されます。
(→WM_COMMANDメッセージ)
WPARAMの下位ワード(LOWORDマクロで取得)にメニュー項目のIDが格納されているので、これを見て処理を割り振ります。
ちなみに上位ワードとLPARAMは常に0です。


#include <windows.h>
#include "resource.h"

//ウィンドウの生成等は省略

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_COMMAND: //コントロールの操作
		switch (LOWORD(wParam))
		{
		case ID_40001:
			MessageBox(hWnd, L"「保存」を実行しました。", L"情報", MB_OK);
			break;
		case ID_40002:
			MessageBox(hWnd, L"終了します。", L"情報", MB_OK);
			SendMessage(hWnd, WM_CLOSE, 0, 0);
			break;
		}
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

これはボタンコントロールと同じなので難しいことはないと思います。
メニュー項目の選択時の動作

識別子が「ID_40001」などの初期値のままだと何のメニュー項目なのかが分かりにくいので、適宜変更すると良いでしょう。
変更する場合は、メニューエディタ上を右クリックして表示されるメニューから「IDの編集」を選択することで、メニューエディタ上から識別子を直接変更できます。
メニューエディタ上で識別子を編集

この方法で編集した場合、「resource.h」の情報も自動で更新されますが、既存のdefine行の更新ではなく新しくdefine行が追加されます。
以前に使用していたdefine行は削除されません。
動作に問題はありませんが、気になる場合は手動で削除してください。

アクセスキーの設定

メニュー項目はAltキーと文字キーとを組み合わせて項目を実行(クリック)することができます。
標準では「Alt + メニュー項目の先頭文字キー」で操作できますが、項目名が日本語の場合は操作ができません。

メニュー項目名の後ろに「&」と任意のキー(アルファベット)を指定することで、Altキーとそのキーとの組み合わせで項目を選択できるようになります。
これをアクセスキーまたはニーモニックキーと言います。
アクセスキーの設定

アクセスキーは下線が引かれた状態になります。
丸括弧は必須ではありませんが、見分けやすいように付けられることが多いです。


/////////////////////////////////////////////////////////////////////////////
//
// Menu
//

IDR_MENU1 MENU
BEGIN
    POPUP "ファイル(&F)"
    BEGIN
        MENUITEM "保存(&S)",                      ID_40001
        MENUITEM SEPARATOR
        MENUITEM "終了(&Q)",                      ID_40002
    END
END

この例では「Alt + F」で「ファイル」メニューが開き、「S」や「Q」キーを押すと指定のメニュー項目を実行できます。
また、メニューバーは「Alt」キー単体を押下することでアクティブ状態になります。
この状態で「F」キーを押下することでも項目を実行できます。

「&」はアクセスキーの指定に使用されるため、項目名に「&」を使用したい場合は「&&」と二つ続けて記述します。