アクセラレータ
 キーボードアクセラレータ
メニューは、キーボードの Alt + 文字キー という組み合わせて操作することができました
しかし、実はこれ以外でも「キーボードアクセラレータ」と呼ばれる方法で
別の自由なキーの組み合わせでメニューを操作することができます
アクセラレータを実装するには、キーの組み合わせと
発生させる ID を組み合わせたアクセラレータテーブルを作成します
これは、リソースクリプトの ACCELERATORS 文を用いることで簡単に実現できます
acctablename ACCELERATORS  [optional-statements]  {
	event, idvalue, [type] [options]
	...
}
acctablename は、このアクセラレータテーブルの名前を指定します
仕様はアイコンなどと同様、文字または16ビット値で指定して下さい
oprional-statements は、MENU 文と同様です
event は、アクセラレータで使われるキーストロークです
ここには、様々なキーストロークを指定することができます(理屈上は全て)
数値を指定してキーコードを指定することも可能ですし、仮想キーも指定できます
ダブルクォーテーションでキャラクタを囲むことで、直接キャラクタを指定することもできます
キャラクタでは、指定方法によって様々なキー表現になります
小文字のキャラクタを指定した場合は、その文字キー単体で選択されます
大文字のキャラクタを指定した場合、デフォルトで Shift + 文字キーの組み合わせです
キャラクタの前に ^ を指定すると
Ctrl + 文字キー という組み合わせにもなります。"^A"のように指定します
idvalue は、アクセラレータを認識する ID を指定します
アクセラレータキーが確認されると、この ID でメッセージが発生します
type には、event で指定したキーストロークのタイプを指定します
これは ASCII または VIRTKEY のいずれかになります
数値でコードを指定したなら ASCII を、仮想キーを指定したなら VIRTKEY を指定します
options はアクセラレータを定義するオプションを指定します
次のいずれかの値を指定することができます
| コード | 解説 | 
|---|
| NOINVERT | アクセラレータが使われた時 トップレベルメニューがハイライトされない
 | 
| ALT | ALT キーが押されている時だけアクセラレータはアクティブになる これは、仮想キーにのみ指定できる
 | 
| SHIFT | SHIFT キーが押されている時だけアクセラレータはアクティブになる これは、仮想キーにのみ指定できる
 | 
| CONTROL | CONTROL キーが押されている時だけアクセラレータはアクティブになる これは、仮想キーにのみ指定できる
 | 
仮想キーコードを指定した時に、ALT キーなどと組み合わせたい場合
options で ALT などを指定する必要があります
次に、ここで作成したアクセラレータテーブルをプログラムにロードします
当然、この時すでに実行ファイルにリソースが含まれている必要があります
アクセラレータは LoadAccelerators() 関数を使って読みこみます
HACCEL LoadAccelerators(HINSTANCE hInstance , LPCTSTR lpTableName);
hInstance には、アクセラレータテーブルのリソースが入ったモジュールのインスタンスを
lpTableName は、アクセラレータテーブルの識別子を指定します
戻り値は、アクセラレータテーブルのハンドル、失敗すれば NULL が返ります
HACCEL 型は、アクセラレータテーブルのハンドルを意味します
さて、ここからが問題ですがここで取得したハンドルをメニューに割り当てるようなことはしません
キーボードアクセラレータは、実はメニューだけのものではないのです
アクセラレータテーブルは、指定されたキーストロークに対してメッセージを発生させるものなのです
つまり、WM_SYSKEYDOWN や WM_KEYDOWN を
WM_SYSCOMMAND や WM_COMMAND に変換するというのがアクセラレータの本来の役割です
この、変換作業を行ってくれるのが TranslateAccelerator() 関数です
この関数に、アクセラレータテーブルのハンドルを渡すのです
int TranslateAccelerator(HWND hWnd , HACCEL hAccTable , LPMSG lpMsg);
hWnd には、メッセージを受け取るウィンドウのハンドル
hAccTable は、有効なアクセラレータテーブルのハンドル
lpMsg は、MSG 構造体へのポインタを指定します
関数が成功すると 0 以外、失敗すると 0 が返ります
この関数が 0 以外の値を返した時は TranslateMessage() による
メッセージの処理を行っては行けません
MSG 構造体を受け取ることから、メッセージループでこの関数を指定することは想像に安いと思います
この関数は、アクセラレータテーブルで定義されているキーストロークを受けると
即座に WM_COMMAND をウィンドウプロシージャに直接送ります
キューにポストするわけではないので、処理が終わるまで制御は返りません
/*resource.h*/
#define IDM_RENA 40001
#define IDM_YUKI 40002
#define IDM_MIMI 40003
/*リソーススクリプト*/
#include "resource.h"
KITTY MENU {
	POPUP "Kitty on your lap(&K)" {
		MENUITEM "レナ(&R)\tCtrl+R" , IDM_RENA
		MENUITEM "ユキ(&Y)\tShift + Y" , IDM_YUKI
		MENUITEM "ミミ(&M)\tm" , IDM_MIMI
	}
}
KITTY ACCELERATORS {
	"^R" , 		IDM_RENA
	VK_END ,	IDM_RENA , VIRTKEY , SHIFT
	0x59 , 		IDM_YUKI , ASCII
	VK_HOME ,	IDM_YUKI , VIRTKEY
	"m" ,		IDM_MIMI
	VK_F1 , 	IDM_MIMI , VIRTKEY , ALT , CONTROL
}
#include <windows.h>
#include "resource.h"
LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_COMMAND:
		switch (LOWORD(wp)) {
		case IDM_RENA:
			SetClassLong(hwnd , GCL_HBRBACKGROUND , 
				(LONG)CreateSolidBrush(RGB(0xFF , 0 , 0))
			);
			break;
		case IDM_YUKI:
			SetClassLong(hwnd , GCL_HBRBACKGROUND , 
				(LONG)CreateSolidBrush(RGB(0 , 0 , 0xFF))
			);
			break;
		case IDM_MIMI:
			SetClassLong(hwnd , GCL_HBRBACKGROUND , 
				(LONG)CreateSolidBrush(RGB(0xFF , 0xAA , 0))
			);
		}
		InvalidateRect(hwnd , NULL , TRUE);
		return 0;
	}
	return DefWindowProc(hwnd , msg , wp , lp);
}
int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow ) {
	HWND hwnd;
	HACCEL haccel;
	MSG msg;
	WNDCLASS winc;
	winc.style		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= WndProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	winc.lpszMenuName	= TEXT("KITTY");
	winc.lpszClassName	= TEXT("KITTY");
	if (!RegisterClass(&winc)) return -1;
	hwnd = CreateWindow(
			TEXT("KITTY") , TEXT("Kitty on your lap") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL ,
			hInstance , NULL
	);
	if (hwnd == NULL) return -1;
	haccel = LoadAccelerators(hInstance , TEXT("KITTY"));
	while(GetMessage(&msg , NULL , 0 , 0)) {
		if (!TranslateAccelerator(hwnd , haccel , &msg)) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}
	return msg.wParam;
}

もちろん、このいい加減な組み合わせのアクセラレータはテスト用です
本来は、システムや他のアプリケーションとの連帯を考えて配置するべきです
IDM_RENA は Ctrl + R と Shift + End キーで発生し
IDM_YUKI は Shift + Y と Home キーで発生し
IDM_MIMI は m キー(単独)と F1 + Alt + Ctrl キーの組み合わせで発生します
WM_COMMAND がメニュー、アクセラレータ、またはコントロールの
どれから発生したかを調べるには WPARAM の上位ワードを調べます
アクセラレータの場合は1、メニューからは0、コントロールならば通知コードになっています
アクセラレータがメニューに対応している場合は WM_INITMENU や
WM_INITMENUPOPUP などのメッセージも発生しています
アクセラレータがメニューに対応していても
ウィンドウやメニュー項目が無効の場合はメッセージが発生しません
LoadAccelerators()
HACCEL LoadAccelerators(HINSTANCE hInstance , LPCTSTR lpTableName);
キーボードアクセラレータテーブルをロードします
hInstance - リソースが入っているモジュールの、インスタンスハンドルを指定します
lpTableName - ロードするキーボードアクセラレータテーブルの名前を指定します
戻り値 - アクセラレータテーブルのハンドル。失敗すると NULL
TranslateAccelerator()
int TranslateAccelerator(HWND hWnd , HACCEL hAccTable , LPMSG lpMsg);
アクセラレータキーストロークを処理します
必要であればメッセージを変換し、プロシージャに直接送ります
関数が成功した場合、TranslateMessage 関数によるメッセージの処理を行なわないでください
hWnd - メッセージを受け取るウィンドウのハンドルを指定します
hAccTable - 参照するアクセラレータテーブルのハンドルを指定します
lpMsg - 有効な MSG 構造体へのポインタを指定します
戻り値 - 成功すると 0 以外、失敗すると 0