NSMenuItem KeyEquivalent“”(空格)错误

2024-01-02

我想为 NSMenuItem (在应用程序主菜单中)设置等效键“”(空格),而无需任何修饰符。

根据文档如下:

例如,在播放媒体的应用程序中,播放命令可能仅映射到“ ”(空格),而没有命令键。您可以使用以下代码来执行此操作:

[menuItem setKeyEquivalent:@" "];

[menuItem setKeyEquivalentModifierMask:0];

等效键设置成功,但不起作用。当我按没有修饰键的“空格”键时,没有任何反应,但当我按“空格”键和“Fn”修饰键时,它会起作用。

我需要使用不带修饰符的“空格”。请帮忙!


这是一个棘手的问题。正如许多答案所暗示的那样,在应用程序或窗口级别拦截事件是强制菜单项工作的可靠方法。同时它也可能会破坏其他东西,例如,如果你有一个专注的NSTextField or NSButton您希望他们消费事件,而不是菜单项。如果用户在系统首选项中重新定义该菜单项的等效键,即更改,这也可能会失败Space to P.

事实上,您使用与菜单项等效的空格键使事情变得更加棘手。空格是特殊的 UI 事件字符之一,与箭头键和其他一些字符一样,AppKit 会以不同的方式对待它们,并且在某些情况下会在传播到主菜单之前消耗它们。

因此,有两件事需要牢记。首先是标准响应者链:

  1. NSApplication.sendEvent将事件发送到关键窗口。
  2. 按键窗口接收事件NSWindow.sendEvent,判断是否为关键事件并调用performKeyEquivalent靠自己。
  3. performKeyEquivalent将其发送到当前窗口firstResponder.
  4. 如果响应者没有使用它,则该事件将被递归地向上发送到nextResponder.
  5. performKeyEquivalent回报true如果其中一个响应者消费了该事件,false否则。

现在,第二个也是棘手的部分,如果事件没有被消耗(即当performKeyEquivalent回报false) the 窗口将尝试将其作为特殊的键盘 UI 事件进行处理 https://i.stack.imgur.com/CfVUW.png– 这在Cocoa 事件处理指南 https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html:

Cocoa 事件调度架构将某些关键事件视为命令,以将控制焦点移动到窗口中的不同用户界面对象、模拟鼠标单击对象、关闭模态窗口以及在允许选择的对象中进行选择。这种能力称为键盘接口控制。键盘界面控制中涉及的大多数用户界面对象都是 NSControl 对象,但非控件的对象也可以参与。

这部分的工作方式非常简单:

  1. 窗户将按键事件转换为相应的操作 https://i.stack.imgur.com/AbpRV.png(选择器)。
  2. 它会与第一响应者检查是否respondsToSelector并调用它。
  3. 如果调用该操作,则事件将被视为已消耗并且事件传播停止。

因此,考虑到所有这些,您必须确保两件事:

  1. 响应者链已正确设置。
  2. 响应者仅消耗他们需要的内容,否则传播事件。

第一点很少会带来麻烦。第二个,这就是你的例子中发生的情况,需要注意 –AVPlayer通常是第一个响应者并使用空格键事件以及其他一些事件。为了使这项工作你需要覆盖keyUp and keyDown方法将事件沿着响应者链传播,就像默认情况下发生的那样NSView执行。

// All player keyboard gestures are disabled.
override func keyDown(with event: NSEvent) {
    self.nextResponder?.keyDown(with: event)
}

// All player keyboard gestures are disabled.
override func keyUp(with event: NSEvent) {
    self.nextResponder?.keyUp(with: event)
}

上面的代码将事件沿着响应者链向上转发,最终将被主菜单接收。如果第一响应者是一个控件,那么有一个问题,例如NSButton或任何自定义NSControl- 继承对象,它WILL消费该事件。通常您确实希望这种情况发生,但如果不希望发生,例如在实现自定义控件时,您可以覆盖respondsToSelector:

override func responds(to selector: Selector!) -> Bool {
    if selector == #selector(performClick(_:)) { return false }
    return super.responds(to: selector)
}

这将防止窗口消耗键盘 UI 事件,因此主菜单可以接收它。但是,如果你想拦截ALL键盘 UI 事件,包括当第一响应者能够使用它时,您确实希望覆盖窗口或应用程序的performKeyEquivalent,但不会像其他答案所暗示的那样重复它:

override func performKeyEquivalent(with event: NSEvent) -> Bool {
    // Attempt to perform the key equivalent on the main menu first.
    if NSApplication.shared.mainMenu?.performKeyEquivalent(with: event) == true { return true }
    // Continue with the standard implementation if it doesn't succeed.
    return super.performKeyEquivalent(with: event)
}

如果你调用performKeyEquivalent在主菜单上而不检查结果,您可能最终会调用它两次 - 第一次是手动,第二次是从super如果事件没有被响应者链消耗,则执行。当AVPlayer是第一响应者并且keyDown and keyUp方法不被覆盖。

附:代码片段是 Swift 4,但想法是一样的! ✌️

附言有一个辉煌WWDC 2010 第 145 场会议 – Cocoa 应用程序中的关键事件处理 https://download.developer.apple.com/videos/wwdc_2010__hd/session_145__key_event_handling_in_cocoa_applications.mov通过优秀的例子深入讨论了这个主题。 Apple 开发者门户网站上不再列出 WWDC 2010-11,但可以找到完整的会议列表here https://meta.stackoverflow.com/a/387544/458356.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

NSMenuItem KeyEquivalent“”(空格)错误 的相关文章

随机推荐