アクセラレータ
キーボードアクセラレータ
メニューは、キーボードの 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