システムメニュー
ウィンドウ制御用のメニュー
メニューはメニューバー、ショートカットメニューのほか、システムメニューがあります。
(現在はウィンドウメニューと呼ぶのが正式名称のようです)
システムメニューはタイトルバーの左端に表示されているアプリケーションアイコンをクリックしたときに表示されるメニューです。
これは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は以下の値をとる
|
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
は独自に追加した動作で、システムメニューを初期状態に戻します。