1、因为加入快捷键, 本例的基础代码和以前不同了; 因为窗体初始化时要先载入快捷键资源, 还要拦截快捷键消息另作处理.
2、关于快捷键的话题, 以前接触过不少了, 但那些都不是本质:
注册系统级热键
所有可选的快捷键列表
快捷键设置控件
快捷键的记录方式
3、我曾经以为快捷键不过就是拦截键盘消息, 现在看来那是想得太简单了; 很容易就可以说明: 键盘消息是需要焦点的, 而快捷键是只对主窗口.
4、使用快捷键前应该先建立一个快捷键表或建立快捷键资源, 本例使用了后者.
快捷键一般是执行菜单的功能, 那它是怎么和菜单挂接的呢? 很简单: 给每一项快捷键和每一个菜单项指定相同的标识即可!
5、在本例中, 窗口建立后, 先用 LoadAccelerators 载入快捷键资源并返回快捷键资源句柄 hAcc;
GetMessage 从消息队列中取出消息后, 先用 TranslateAccelerator 检查是否是快捷键消息. 如果是, TranslateAccelerator 会把此快捷键消息转换成菜单消息(WM_COMMAND, 也可能是系统菜单消息: WM_SYSCOMMAND), 然后直接发送给窗口; 如果不是, 再继续以前的消息传递.
其实快捷键消息的本质也是键盘消息, 不过 TranslateAccelerator 会检查键盘消息的组合是否是我们定义的快捷键. 但我们在执行快捷键时, 焦点未必就在主窗口, 它是怎么吧其他窗口的键盘消息转给主窗口的呢?
如果 GetMessage 函数的第二个参数为 0 时, 它会检索程序中所有窗口的消息, TranslateAccelerator 接过来后, 不管消息来自那个窗口, 都把消息包中的窗口句柄换成主窗口句柄再传递.
关于快捷键资源文件还是解释在资源文件下面吧...
本例效果图:
本例使用的资源文件(TestRes.rc):
MyMenu1 MENUEX BEGIN POPUP "菜单" BEGIN MENUITEM "菜单_1\t Shift+1" ,101 MENUITEM "菜单_2\t Ctrl+2" ,102 MENUITEM "菜单_3\t Alt+3" ,103 MENUITEM "菜单_4\t Shift+Ctrl+Alt+4" ,104 MENUITEM "菜单_5\t 5" ,105 MENUITEM "菜单_6\t F5" ,106 MENUITEM "菜单_7\t Ctrl+A" ,107 MENUITEM "菜单_8\t Alt+A" ,108 END END MyAccel ACCELERATORS BEGIN "1", 101, VIRTKEY, SHIFT, NOINVERT "2", 102, VIRTKEY, CONTROL, NOINVERT "3", 103, VIRTKEY, ALT, NOINVERT "4", 104, VIRTKEY, SHIFT, CONTROL, ALT, NOINVERT "5", 105, VIRTKEY, NOINVERT, NOINVERT VK_F5, 106, VIRTKEY, NOINVERT, NOINVERT "A", 107, VIRTKEY, CONTROL "a", 108, ASCII, ALT END注释:
1、快捷键资源文件关键字: ACCELERATORS, 需要取个名给程序中的 LoadAccelerators 使用, 这里是: MyAccel.
2、快捷键资源文件可以有五组参数, 从后面说: NOINVERT 是让被激活的菜单的顶层菜单不闪烁(但我去掉也没看出来).
它前面是控制键: SHIFT、CONTROL、ALT.
再前面有两个选择: VIRTKEY、ASCII, VIRTKEY 指示快捷键要用 虚拟键码 说明; ASCII 指示用 ASCII 码说明快捷键. 譬如例子中的 VK_F5 就是典型的虚拟键码. 但常规数字键和字母键没有对应的虚拟键码, 对数字(非小键盘数字)来讲, 这两者没有区别; 特别注意: VIRTKEY 时指示的字母须大写、ASCII 指示的字母须小写.
3、这其中最重要的就是第二个参数了, 它必须和菜单的标识相对应.
4、另外, 菜单资源也和以前有点区别: 在 \t (Tab 的意思) 后面用文本说明了对应的快捷键, 不过这只是显示效果而已, 有没有都不影响功能.
本例代码文件:
program Project1; {$R 'TestRes.res' 'TestRes.rc'} uses Windows, Messages; {收到 WM_COMMAND 消息时需要做的工作} procedure OnCommand(h: HWND; wParam: Integer); var W: Word; arr: array[Byte] of Char; begin W := LoWord(wParam); case W of 101..108: begin GetMenuString(GetMenu(h), W, arr, Length(arr), MF_BYCOMMAND); SetWindowText(h, arr); end; end; end; function WndProc(wnd: HWND; msg: UINT; wParam: Integer; lParam: Integer): Integer; stdcall; begin Result := 0; case msg of WM_COMMAND : OnCommand(wnd, wParam); {收到 WM_COMMAND 消息后调用 OnCommand 过程} WM_DESTROY : PostQuitMessage(0); else Result := DefWindowProc(wnd, msg, wParam, lParam); end; end; function RegMyWndClass: Boolean; var cls: TWndClass; begin cls.style := CS_HREDRAW or CS_VREDRAW; cls.lpfnWndProc := @WndProc; cls.cbClsExtra := 0; cls.cbWndExtra := 0; cls.hInstance := HInstance; cls.hIcon := 0; cls.hCursor := LoadCursor(0, IDC_ARROW); cls.hbrBackground := HBRUSH(COLOR_WINDOW + 1); cls.lpszMenuName := 'MyMenu1'; cls.lpszClassName := 'MyWnd'; Result := RegisterClass(cls) <> 0; end; {程序入口} const tit = 'New Form'; ws = WS_OVERLAPPEDWINDOW; x = 100; y = 100; w = 300; h = 180; var hWnd: THandle; Msg : TMsg; hAcc: HACCEL; {定义快捷键资源句柄} begin RegMyWndClass; hWnd := CreateWindow('MyWnd', tit, ws, x, y, w, h, 0, 0, HInstance, nil); ShowWindow(hWnd, SW_SHOWNORMAL); UpdateWindow(hWnd); hAcc := LoadAccelerators(HInstance, 'MyAccel'); {载入快捷键资源} while(GetMessage(Msg, 0, 0, 0)) do begin if (TranslateAccelerator(hwnd, hAcc, Msg) = 0) then {拦截并处理快捷键消息} begin TranslateMessage(Msg); DispatchMessage(Msg); end; end; end.