ショートカットメニュー

任意の位置にメニューを表示する

メニューはメニューバーとしてウィンドウの上部に配置するほか、任意の場所に表示することができます。
多くのアプリケーションではクライアント領域を右クリックしたときに、マウスカーソルの位置に何らかのメニューが表示される作りになっています。
そのため「右クリックメニュー」とも呼ばれますが、(Windowsでは)正式にはショートカットメニューと言います。
「ポップアップメニュー」や「コンテキストメニュー」とも言います。

サブメニューの取得

ショートカットメニューはメニューバーとは違い「バー」が無く、メニューバー項目のクリック時にドロップダウン表示されるサブメニューのみを表示することになります。
サブメニューを取得するにはGetSubMenu関数を使用します。

HMENU GetSubMenu(
 HMENU hMenu,
 int nPos
);
メニューハンドルhMenuからnPos番のサブメニューを取得する。
存在しない場合はNULLを返す。

hMenuLoadMenu関数で取得されるメニューハンドルを指定します。
nPosはサブメニューを開く親となるメニューの項目の番号です。
(最初は0)

TrackPopupMenuEx関数

実際にショートカットメニューを開くにはTrackPopupMenuEx関数を使用します。

BOOL TrackPopupMenuEx(
 HMENU hMenu,
 UINT uFlags,
 int x,
 int y,
 HWND hWnd,
 LPTPMPARAMS lptpm
);
ショートカットメニューhMenuを開く。
成功した場合は0以外を、失敗した場合は0を返す。
フラグuFlagsにTPM_RETURNCMDフラグを指定した場合、戻り値は選択したメニュー項目の識別子。
何も選択せずにメニューが閉じられた場合は0を返す。

hMenuGetSubMenu関数で取得されるサブメニューのハンドルです。

uFlagsはメニューの動作に関するフラグの指定です。
以下の定数の組み合わせを指定します。

定数 説明
表示位置の指定
TPM_LEFTALIGN メニューの左端位置を引数xの位置にする
(既定)
TPM_CENTERALIGN メニューの水平方向の中央位置を引数xの位置にする
TPM_RIGHTALIGN メニューの右端位置を引数xの位置にする
TPM_TOPALIGN メニューの上端位置を引数yの位置にする
(既定)
TPM_VCENTERALIGN メニューの垂直方向の中央位置を引数yの位置にする
TPM_BOTTOMALIGN メニューの下端位置を引数yの位置にする
メッセージ関連
TPM_NONOTIFY 親ウィンドウに一部の通知メッセージを送信しない
(WM_COMMANDは送信される)
TPM_RETURNCMD 選択した項目の識別子をこの関数の戻り値として返す
(WM_COMMANDは送信されなくなる)
マウスボタンの動作
TPM_LEFTBUTTON マウスボタンの左クリックでのみ項目を選択可能
(既定)
TPM_RIGHTBUTTON マウスボタンの左または右クリックで項目を選択可能
アニメーション
TPM_HORPOSANIMATION メニューを左から右にアニメーションする
TPM_HORNEGANIMATION メニューを右から左にアニメーションする
TPM_VERPOSANIMATION メニューを上から下にアニメーションする
TPM_VERNEGANIMATION メニューを下から上にアニメーションする
TPM_NOANIMATION アニメーションしない
その他
TPM_HORIZONTAL 表示しようとするメニューの領域が除外領域(lptpm)に重なるとき、水平方向にずらして表示する。
TPM_VERTICAL 表示しようとするメニューの領域が除外領域(lptpm)に重なるとき、垂直方向にずらして表示する。
TPM_RECURSE 別のメニューが表示されている状態でもメニューを開く
TPM_LAYOUTRTL テキストを右から左に表示

アニメーションの設定に関してはWindowsの設定でメニューのアニメーション表示が有効になっている必要があります。

xyはメニューを表示する位置の基準点となるX座標とY座標です。
これはスクリーン座標を指定します。

hWndはショートカットメニューを所有するウィンドウのハンドルです。
このウィンドウにメッセージが送られてきます。

lptpmはメニューの表示を禁止する領域の指定です。
(スクリーン座標)
メニューはこの領域内に重なって表示されることはありません。
必要がない場合はNULLを指定します。
データ型はTPMPARAMS構造体(のポインタ)で、以下のようになっています。

typedef struct tagTPMPARAMS {
 UINT cbSize;
 RECT rcExclude;
} TPMPARAMS;
ウィンドウの配置時に除外する矩形を格納する構造体。

cbSizeメンバはこの構造体のサイズです。
(sizeof(TPMPARAMS)を指定)
rcExcludeメンバはRECT構造体です。

TrackPopupMenuEx関数とほぼ同じものにTrackPopupMenu関数があります。
こちらを使用することも可能ですが、これから新規に作るプログラムではTrackPopupMenuEx関数の使用が推奨されます。

WM_CONTEXTMENUメッセージ

ショートカットメニューはクライアント領域を右クリックすることで表示されますが、「Shift + F10」キーにもこの機能が割り当てられています。
その他、キーボードに専用のキーが搭載されている機種もあります。
これらの操作ではWM_CONTEXTMENUメッセージが送信されます。

WPARAMは右クリックしたウィンドウのハンドルです。
このハンドルは右クリックした位置によってはウィンドウ上の子ウィンドウ(コントロール)のハンドルである可能性があります。

LPARAMの下位ワードは右クリック時のマウスカーソルのX座標です。
上位ワードはマウスのY座標です。
(スクリーン座標)

下位ワード/上位ワードはLOWROD/HIWORDマクロで取得できますが、このメッセージの処理ではGET_X_LPARAM/GET_Y_LPARAMマクロを使用する必要があります。
(→マウス座標)
右クリック以外でこのメッセージを発生させたとき(「Shift + F10」キーなど)、マウスの座標情報は(-1, -1)という値になります。
これをLOWROD/HIWORDマクロで取得すると正数(プラス値)になってしまい、おかしな位置にショートカットメニューが表示されてしまいます。
GET_X_LPARAM/GET_Y_LPARAマクロならば負数(マイナス値)で取得できるので、負数の場合は位置を調整する処理をはさむことができます。

サンプルコード

以下にショートカットメニューのサンプルを示します。


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

IDR_MENU1 MENU
BEGIN
    POPUP "右クリック1"
    BEGIN
        MENUITEM "アイテム1",                       ID_40001
        MENUITEM "アイテム2",                       ID_40002
        MENUITEM "ショートカットメニュー2に切り替え",           ID_40003
    END
    POPUP "右クリック2"
    BEGIN
        MENUITEM "アイテム3",                       ID_40004
        MENUITEM "アイテム4",                       ID_40005
        MENUITEM "ショートカットメニュー1に切り替え",           ID_40006
    END
END

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

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

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HMENU hMenuP, hMenu1, hMenu2, hMenu;
	POINT point;
	switch (message)
	{
	case WM_CREATE:
		hMenuP = LoadMenu(hInst, MAKEINTRESOURCE(IDR_MENU1));
		hMenu1 = GetSubMenu(hMenuP, 0);
		hMenu2 = GetSubMenu(hMenuP, 1);
		hMenu = hMenu1;
		break;

	case WM_CONTEXTMENU: //ショートカットメニュー表示
		point.x = GET_X_LPARAM(lParam);
		point.y = GET_Y_LPARAM(lParam);
		if (point.x < 0) {//右クリック以外
			//クライアント領域の左上に表示する
			point.x = point.y = 0;
			ClientToScreen(hWnd, &point);
		}
		TrackPopupMenuEx(hMenu, TPM_LEFTALIGN | TPM_TOPALIGN,
			point.x, point.y, hWnd, NULL);
		break;

	case WM_COMMAND: //コントロールの操作
		switch (LOWORD(wParam))
		{
		case ID_40003:
			hMenu = hMenu2;
			MessageBox(hWnd, L"ショートカットメニューを「2」に切り替えました。", L"情報", MB_OK);
			break;
		case ID_40006:
			hMenu = hMenu1;
			MessageBox(hWnd, L"ショートカットメニューを「1」に切り替えました。", L"情報", MB_OK);
			break;
		}
		break;

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

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

クライアント領域内を右クリックするとショートカットメニューが表示されます。
「ショートカットメニュー○に切り替え」の項目を選択するごとにメニューの内容が交互に切り替わります。
ショートカットメニューを表示するサンプル

まずLoadMenu関数で親メニューハンドルを取得します。
親メニューは「右クリック1」「右クリック2」の2つの項目があり、それぞれ3つずつの項目を持つサブメニューを持ちます。
この親メニューからGetSubMenu関数でサブメニューハンドルを2つ取得します。

WM_CONTEXTMENUメッセージでは、ショートカットメニューを実際に表示する処理を記述します。
LPARAMから位置を取得し、正数だった場合は右クリックでの呼び出しなので、そのままの位置(マウスカーソルの位置)に表示します。
負数だった場合は、今回はクライアント領域の左上に表示することにします。
(→スクリーン座標とクライアント座標の変換)
アプリケーションによっては現在選択中のコントロールの位置に表示するなどすれば使いやすくなるでしょう。

TrackPopupMenuEx関数ではTPM_LEFTALIGNフラグとTPM_TOPALIGNフラグを指定し、表示位置から右下方向にメニューが表示されるようにしています。
(これらのフラグを指定しなくても既定で同じ動作になります)

トとビット演算 ≫