アクセラレータ


キーボードアクセラレータ

メニューは、キーボードの 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



前のページへ戻る次のページへ