EternalWindows
メニュー / システムメニュー

システムメニューとは、ウインドウタイトルの左横にあるアイコンを クリックしたときに表示されるメニューのことで、ウインドウメニューや コントロールメニューと呼ばれることもあります。 このメニューには独自の項目を追加することが可能で、 自分の好きなようにカスタマイズしても問題ありません。 システムメニューのハンドルは、GetSystemMenuで取得します。

HMENU GetSystemMenu(
    HWND hWnd,
    BOOL bRevert
);

hWndは、システムメニューを持つウインドウのハンドルを指定します。 bRevertは、カスタマイズしたシステムメニューを元に戻すためのもので、 もし、この引数がTRUEである場合、システムメニューはデフォルトに戻ります。 FALSEの場合は、システムメニューのハンドルが返ります。

今回のプログラムはウインドウを中央配置する項目と、 デフォルトに戻す項目をシステムメニューに追加します。

#include <windows.h>

#define ID_CENTER 10
#define ID_REVERT 20

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId, int nPos);
void CenterWindow(HWND hwnd);
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;
		int   nItemCount;
		
		hmenu = GetSystemMenu(hwnd, FALSE);
		
		InitializeMenuItem(hmenu, TEXT("中央配置(&C)"), ID_CENTER, 2);

		nItemCount = GetMenuItemCount(hmenu);
		InitializeMenuItem(hmenu, TEXT("元のメニューに戻す(&R)"), ID_REVERT, nItemCount);

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

		if (nId == ID_CENTER)
			CenterWindow(hwnd);
		else if (nId == ID_REVERT)
			GetSystemMenu(hwnd, TRUE);
		else
			break;

		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, int nPos)
{
	MENUITEMINFO mii;
	
	mii.cbSize     = sizeof(MENUITEMINFO);
	mii.fMask      = MIIM_ID | MIIM_TYPE;
	mii.fType      = MFT_STRING;
	mii.wID        = nId;
	mii.dwTypeData = lpszItemName;

	InsertMenuItem(hmenu, nPos, TRUE, &mii);
}

void CenterWindow(HWND hwnd)
{
	int  x, y;
	int  nScreenWidth, nScreenHeight;
	RECT rc;

	GetWindowRect(hwnd, &rc);
	
	nScreenWidth  = GetSystemMetrics(SM_CXFULLSCREEN);
	nScreenHeight = GetSystemMetrics(SM_CYFULLSCREEN);

	x = (nScreenWidth - (rc.right - rc.left)) / 2;
	y = (nScreenHeight - (rc.bottom - rc.top)) / 2;

	SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE);
}

システムメニューに項目を追加しているのは、WM_CREATEです。

case WM_CREATE: {
	HMENU hmenu;
	int   nItemCount;
	
	hmenu = GetSystemMenu(hwnd, FALSE);
	
	InitializeMenuItem(hmenu, TEXT("中央配置(&C)"), ID_CENTER, 2);

	nItemCount = GetMenuItemCount(hmenu);
	InitializeMenuItem(hmenu, TEXT("元のメニューに戻す(&R)"), ID_REVERT, nItemCount);

	return 0;
}

今回のInitializeMenuItemの第4引数は、項目を追加する位置です。 これまで通り、InsertMenuItemを呼び出していては、 項目が最後に追加されてしまうため、このような引数を要求しています。

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

	InsertMenuItem(hmenu, nPos, TRUE, &mii);
}

InsertMenuItemの呼び出しで第3引数がTRUEとなっています。 これは、第2引数が項目の識別子ではなく位置を表していることを意味しています。 項目を任意の位置に追加したい場合は、第3引数はTRUEにすべきです。 位置は最初の項目が0にあたり、以下、1つずつインクリメントしていきます。

nItemCount = GetMenuItemCount(hmenu);
InitMenuItem(hmenu, TEXT("元のメニューに戻す(&R)"), ID_REVERT, nItemCount);

元のメニューに戻すという項目は風変わりなので最後に追加しようと思ったのですが、 そうなると項目の数を知っておかなければなりません。 これは、GetMenuItemCountを取得できます。 といっても、最後の項目ならInsertMenuItemの第3引数をFALSEにする方法も候補です。

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

	if (nId == ID_CENTER)
		CenterWindow(hwnd);
	else if (nId == ID_REVERT)
		GetSystemMenu(hwnd, TRUE);
	else
		break;

	return 0;
}

システムメニューの項目の選択の通知は、WM_SYSCOMMANDとして送られます。 ID_REVERTのときはシステムメニューを元に戻すわけですから、 GetSystemMenuの第2引数はTRUEとなります。 else文は独自に追加した項目以外を選択した場合に該当しますが、 ここでbreakを実行しているのは非常に重要です。 デフォルトの項目はシステムに処理させる必要があるので、 DefWindowProcに任せなければなりません。

古くなったメニュー関数

メニュー項目の追加や変更は、プログラマにとって負担の大きいものです。 InsertMenuItemやSetMenuItemInfoの呼び出しは構造体が必要になることから、 それに要するコード量は多くなり、柔軟性はあるものの使いにくさを否めません。 しかし、メニュー関数の中には追加や特定の状態変更のみに特化した関数も用意されており、 推奨はされいなものの、今もなお呼び出すことが可能となっています。

AppendMenu(hmenuPopup, MF_STRING, ID_EXIT, TEXT("終了(&X)"));

たとえば、このAppendMenuという関数はメニューの最後に新しい項目を追加し、 その項目の識別子を第3引数に、文字列を第4引数に設定します。 第2引数のフラグには、文字列の追加を意味するMF_STRING以外に、 MF_SEPARATORやMF_CHECKEDなども指定できるため、 この関数で十分にInsertMenuItemの機能を満たすことができます。 しかし、リファレンスにAppendMenuがInsertMenuItemに取って代わられたと書かれている以上、 やはりInsertMenuItemを利用し続けるのが安全だといえるでしょう。 ちなみに、AppendMenuのような古くなったメニュー関数はこの他に、 GetMenuStateやGetMenuStringがあり、これらはGetMenuItemInfoに取って代わられています。 また、ModifyMenuやCheckMenuItemは、SetMenuItemInfoに取って代わられています。 しかし、EnableMenuItemという項目を有効にする関数は古いとされていないので、 この関数については特に気にせず呼び出していけばよいと思われます。



戻る