何时会触发快捷键?

16

昨天我发现了一种情况,当我期望触发一个键盘快捷键时,它并没有被触发。

具体情况是:当我在MDI子窗口上按下ActionList的操作的快捷键组合时,MDI表单上的侧边栏处于焦点状态。

我一直以为快捷键可以全局使用。在什么情况下会或不会触发快捷键?


你能准确地解释一下什么是侧边栏吗? - David Heffernan
@David 类似这样的内容 - NGLN
我猜重要的是它在 mdi 表单中的位置。 - David Heffernan
根据答案,根据接受的答案,快捷方式应该已经被触发了,因为活动窗体具有ActionList,除非某种情况下禁用了该操作。那是不是这种情况呢? - boggy
@costa,MDI 子窗体没有激活,而是 MDI 主窗体激活了。 - NGLN
1个回答

32
这是一个貌似简单实则复杂的问题,需要一些基础知识和对 VCL 代码的快速解读,最终得出令人满意的答案。
什么是 ShortCut?
ShortCut 是一种特殊的键盘组合,由一个或多个按键触发相应操作。"特殊" 意味着在程序员看来这些按键有着特定含义。
在 Delphi 中,ShortCut 类型为 TShortCut,被定义为整数类型的 Word 范围内 (0..65535) 的值。通常,一个 ShortCut 是由多个按键组成,例如:
CTRL + K = scCtrl + Ord('K') = 16384 + 75 = 16459。
如何指定 ShortCut?
ShortCuts可被分配给Action的ShortCut或SecondaryShortCuts属性,或MenuItem的ShortCut属性,从而在按下ShortCut的键盘组合时调用该Action的OnExecute事件或MenuItem的OnClick事件。为了处理Action的ShortCut,需要将Action启用,并将其添加到未挂起的ActionList中或附加到已启用的MenuItem中。同样,为了处理MenuItem的ShortCut,需要将MenuItem添加到菜单中。

ShortCuts还可以从ApplicationFormApplicationEventsOnShortCut事件中解释。在这些事件中,Msg参数将在其CharCode成员中保存按键代码,并且可能使用GetKeyState提取特殊键,如ShiftCtrlAlt

procedure TForm1.FormShortCut(var Msg: TWMKey; var Handled: Boolean);
begin
  if (Msg.CharCode = Ord('K')) and (GetKeyState(VK_CONTROL) < 0) then
  begin
    Caption := 'CTRL+K pressed';
    Handled := True;
  end;
end;

如果Handled参数设置为True,则键的任何后续处理都将被跳过。
如何捕获快捷键?
VCL不会保留所有指定快捷键的列表。(它怎么可能呢?)因此,所有按键都有可能成为快捷键。这正是VCL解释快捷键的方式:通过评估每个按下的键。
Peter Below撰写了一篇关于A Key's Odyssey通过VCL的优秀而全面的文章。简而言之,捕获快捷键的方法如下:
  • TApplication.Run接收应用程序发送的每个Windows消息,
  • TApplication.ProcessMessage调用IsKeyMsg,如果是WM_KEYDOWN消息,则将消息传递给聚焦的控件CN_KEYDOWN消息处理程序。
如何处理快捷键?

TWinControl.CNKeyDown 检查按键是否为菜单键(我们将看到这里的 菜单 定义超出了物理菜单):

  • TWinControl.IsMenuKey 首先检查键是否是控件或其父级菜单中的 PopupMenu 中的 ShortCut,
    • TMenu.IsShortCut 1) 遍历所有子菜单项并调用启用了 ShortCut 的 MenuItem 的 OnClick 事件处理程序,
  • 如果没有被处理,则检查键是否是控件所在窗体的 ShortCut,调用 TCustomForm.IsShortCut 2),
    • 如果分配了 OnShortCut 窗体事件,则会调用该事件,
    • 如果未处理,则检查键是否位于 窗体的 MainMenu 中(参见 1)),
    • 如果仍未处理,则将该键发送到窗体所拥有的所有 ActionLists (仍然是指活动窗体)。在 Delphi 版本 10(BDS2006)之前,这些 ActionLists 需要直接归属于窗体,并保存在受保护的字段 FActionLists 中(当需要时可以进行干预)。这被认为是一个错误,从 BDS2006 开始,该字段被删除,ActionLists 也可以间接地归属于窗体。
      • TCustomActionList.IsShortCut 遍历其所有 Actions 并使用其 ShortCutSecondaryShortCuts 属性中设置的 ShortCut 调用启用了 Action 的 HandleShortCut
  • 如果仍未处理,则调用 Application.IsShortCut(通过 CM_APPKEYDOWN),
    • 触发 OnShortCut 应用事件,其中包括项目中的所有 ApplicationEvents 组件的 OnShortCut 事件(如果已分配),
    • 如果未处理,则调用 MainFormIsShortCut 程序(参见 2)),但 仅当 MainForm 已启用时。例如,当活动窗体为模态窗体时,MainForm 将被禁用。这将触发 MainForm 的 OnShortCut 事件 或遍历 Mainform 直接或间接拥有的ActionLists(取决于上述 Delphi 版本)。

那么,什么时候会处理快捷键?

当它:

  • 为弹出菜单中启用的菜单项设置,并且该弹出菜单附加到当前聚焦的控件或其任何父级上,
  • 为主菜单中启用的菜单项设置,并且该主菜单显示在当前活动窗体或主窗体上,但仅当主窗体已启用时,
  • 为未挂起的操作列表中所拥有的、属于当前活动窗体或主窗体的启用操作设置,但仅当主窗体已启用时,
  • 在当前活动窗体或主窗体的OnShortCut事件中捕获,但仅当主窗体已启用时,
  • 在应用程序或任何ApplicationEvents组件的OnShortCut事件中捕获。

那么,什么时候不会处理快捷键?

当它被设置为:

  • 禁用的菜单项,
  • 没有下拉菜单的菜单项,
  • 位于主菜单中但未附加到窗体上的菜单项,
  • 位于弹出菜单中且附加到兄弟菜单项上的菜单项,
  • 被禁用的操作,
  • 未包含在操作列表中的操作,
  • 位于操作列表中但不属于当前活动窗体或主窗体所拥有的操作。例如:另一个窗体、数据模块、应用程序、实用工具单元等...
  • 在 Delphi 版本 BDS2006 以下,不直接属于当前活动窗体或主窗体所拥有的操作。

感谢您写得非常好的总结 - 我知道Peter的文章很全面,但您很容易在那篇文章中迷失 ;) - Edwin Yip
所以在阅读了您的回答后,我检查了TCustomForm.IsShortCut,似乎即使例如FormChild是在FormParent中作为父级/嵌套的,但如果它不是由FormParent拥有,那么如果它当前没有焦点,FormChild中的快捷键也不会被处理? - Edwin Yip
是的,在实验后,我确认我的上面评论的答案是肯定的。 - Edwin Yip

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接