システムメニュー

ウィンドウ制御用のメニュー

メニューはメニューバーショートカットメニューのほか、システムメニューがあります。
(現在はウィンドウメニューと呼ぶのが正式名称のようです)
システムメニューはタイトルバーの左端に表示されているアプリケーションアイコンをクリックしたときに表示されるメニューです。
これはWindowsが提供するもので、CreateWindow関数のウィンドウスタイルにWS_SYSMENUフラグを含めることで表示されるようになります。
システムメニュー

何もプログラミングしていない状態でもウィンドウの移動や最大化などの操作が可能です。

メッセージ

システムメニューを開いたり項目を選択したとき、親ウィンドウにWM_SYSCOMMANDメッセージが送信されます。
(正確には、Windowsが提供するウィンドウ操作の機能を実行しようとしたとき。例えばウィンドウ右上の「閉じる」等のボタンやウィンドウの移動など)

WPARAMには以下の定数のいずれかが格納されています。
(コマンドID)

定数 説明
SC_SIZE ウィンドウサイズの変更
SC_MOVE ウィンドウの移動
SC_MINIMIZE ウィンドウの最小化
SC_MAXIMIZE ウィンドウの最大化
SC_NEXTWINDOW 次のウィンドウに移動
SC_PREVWINDOW 前のウィンドウに移動
SC_CLOSE ウィンドウを閉じる
SC_VSCROLL 垂直スクロール
SC_HSCROLL 水平スクロール
SC_MOUSEMENU マウスクリックによってシステムメニューが取得された
SC_KEYMENU キー操作によってシステムメニューが取得された
SC_RESTORE ウィンドウの位置とサイズを元に戻す
SC_TASKLIST スタートメニューをアクティブにする
SC_SCREENSAVE スクリーンセーバーを実行する
(system.iniファイルの[boot]セクションで指定)
SC_HOTKEY アプリケーション定義のホットキーに関連付けられたウィンドウ(LPARAMで指定)をアクティブにする
SC_DEFAULT デフォルトの項目を選択する
(デフォルトの項目=アプリケーションアイコンをダブルクリックで実行される項目)
SC_MONITORPOWER ディスプレイの状態を設定する
省電力機能を持つ機器で有効
LPARAMは以下の値をとる
  • -1 - 電源がオン
  • 1 - 省電力を有効にする
  • 2 - 電源がオフ
SC_CONTEXTHELP マウスカーソルをクエスチョンマーク付きのポインタに変更する
この状態でユーザーがダイアログボックス内のコントロールをクリックすると、コントロールはWM_HELPメッセージを受信する
SCF_ISSECURE スクリーンセーバーがセキュアか否かを示す
(具体的な使用方法は不明)

これらの動作はDefWindowProc関数が提供しています。
動作を変更したい場合はウィンドウプロシージャでWM_SYSCOMMANDメッセージを捕捉し、WPARAMの値によって処理を割り振ります。

ただし通常の比較(等価演算子==やswitch文など)では正しく一致しないことがあります。
WPARAMの値にGET_SC_WPARAMというマクロを通すことで、上記の定数と一致する値を得ることができます。

WM_SYSCOMMANDメッセージのWPARAMは、下位4ビットが内部的に使用されているそうです。
上記の定数の実際の値はすべて下位4ビットは0で定義されています。
(SCF_ISSECURE以外)
例えばSC_CLOSEフラグの実際の値は0xF060ですが、「0xF061」というような値が送られてくる可能性があり、そのまま比較しても一致しないことがあります。
(16進数の桁ひとつは4ビット分)

GET_SC_WPARAMマクロは下位4ビットを削る処理をしています。
Visual Studioでは以下のように定義されています。


#define GET_SC_WPARAM(wParam) ((int)wParam & 0xFFF0)

//以下のように使用する
case  WM_SYSCOMMAND:
	switch (GET_SC_WPARAM(wParam))
	{
	case SC_CLOSE:
		//何か処理
	}
	return DefWindowProc(hWnd, message, wParam, lParam);

ウィンドウプロシージャでWM_SYSCOMMANDメッセージを捕捉する場合、処理の目的としていないIDが送られた来た時はDefWindowProc関数を実行してください。
そうしないとシステムメニューが正しく動作しなくなります。
DefWindowProc関数に渡すWPARAMは加工前のものをそのまま渡してください。

LPARAMは、コマンドIDがSC_MOUSEMENUの時にマウスの座標が格納されています。
座標の取得方法はマウス座標の項を参照してください。
また、コマンドIDがSC_KEYMENUの時に、システムメニュー内の項目を実行するのに使用したキーの文字コードが格納されています。
例えば「Alt + X」を実行した場合は「x」が格納されています。
これら以外のコマンドIDではLPARAMは使用されません。

項目の追加

システムメニューのハンドルはGetSystemMenu関数で取得することができます。

HMENU GetSystemMenu(
 HWND hWnd,
 BOOL bRevert
 );
ウィンドウhWndのシステムメニューハンドルを取得する。

第二引数のbRevertは、FALSEを指定するとシステムメニューのハンドルを返します。
TRUEを指定するとシステムメニューを初期状態にリセットします。
このときの戻り値はNULLです。

このハンドルを通して、システムメニューに項目を追加することができます。

厳密にはFALSEを指定するとシステムメニューのコピーのハンドルを返すらしいです。
TRUEを指定してメニューを初期状態に戻すとき、項目を追加した(コピーの)メニューは自動で破棄されます。

項目の追加はメニューの動的生成で使用したInsertMenuItem関数で行います。
ただし既存の項目との重複を防ぐため、追加する項目の識別子の値は0xF000未満にする必要があります。
(0xF000 = 61440)
また、独自の項目を処理した場合はDefWindowProc関数は実行しないでください。

識別子の下位4ビットを0以外にするとGET_SC_WPARAMマクロで加工すると一致しなくなります。
そのため、下位4ビットは0にしておくことをおすすめします。

サンプルコード

システムメニューのメッセージ処理と項目の追加のサンプルです。


#include <windows.h>

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

#define ID_RESETSYSMENU					0x0010

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HMENU hMenu;
	MENUITEMINFO mii;

	switch (message)
	{
	case WM_CREATE:
		//システムメニューのハンドルを取得
		hMenu = GetSystemMenu(hWnd, FALSE);

		mii.cbSize = sizeof(MENUITEMINFO);
		mii.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_ID;
		mii.fType = MFT_STRING;

		mii.dwTypeData = L"スタートメニューをアクティブにする";
		mii.wID = SC_TASKLIST;
		InsertMenuItem(hMenu, 0, TRUE, &mii);

		mii.dwTypeData = L"メニューを初期状態に戻す";
		mii.wID = ID_RESETSYSMENU;
		InsertMenuItem(hMenu, 1, TRUE, &mii);
		break;

	case  WM_SYSCOMMAND:
		switch (wParam)
		{
		case ID_RESETSYSMENU:
			//システムメニューを初期化
			GetSystemMenu(hWnd, TRUE);
			return 0;
		}
		//DefWindowProc関数を呼ぶ
		return DefWindowProc(hWnd, message, wParam, lParam);

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

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

システムメニューのメッセージ処理と項目の追加のサンプル

SC_TASKLISTはシステム定義済みの動作で、DefWindowProc関数によって処理されます。
実行するとWindowsのスタートメニューが開きます。
ID_RESETSYSMENUは独自に追加した動作で、システムメニューを初期状態に戻します。