NSMenuItem KeyEquivalent""(空格)错误

Kir*_*ira 9 cocoa keyboard-shortcuts nsmenuitem

我想为NSMenuItem设置关键等效""(空格)而不使用任何修饰符(在App主菜单中).

如下文档:

例如,在播放媒体的应用程序中,可以在没有命令键的情况下将播放命令映射到"(空格)".您可以使用以下代码执行此操作:

[menuItem setKeyEquivalent:@""];

[menuItem setKeyEquivalentModifierMask:0];

Key Equivalent设置成功,但不起作用.当我按下没有修饰符的"Space"键时没有任何反应,但是当我用"Fn"修饰键按下"Space"时它会起作用.

我需要使用没有修饰符的"空格".请帮忙!

Ian*_*hek 5

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

\n\n

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

\n\n

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

\n\n
    \n
  1. NSApplication.sendEvent将事件发送到关键窗口。
  2. \n
  3. 按键窗口接收 中的事件NSWindow.sendEvent,判断是否为按键事件并调用performKeyEquivalent自身。
  4. \n
  5. performKeyEquivalent将其发送到当前窗口的firstResponder.
  6. \n
  7. 如果响应者不使用它,该事件将被递归地向上发送到nextResponder.
  8. \n
  9. performKeyEquivalenttrue如果响应者之一消耗了该事件,则返回,否则返回false
  10. \n
\n\n

现在,第二个也是棘手的部分,如果事件没有被消耗(即当performKeyEquivalentreturns时false,窗口将尝试将其作为特殊的键盘 UI 事件\xe2\x80\x93 进行处理,这在Cocoa 事件处理中简要提到指导

\n\n
\n

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

\n
\n\n

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

\n\n
    \n
  1. 窗口将按键事件转换为相应的操作(选择器)。
  2. \n
  3. 它会检查第一响应者是否存在respondsToSelector并调用它。
  4. \n
  5. 如果调用该操作,则该事件将被视为已消耗并且事件传播停止。
  6. \n
\n\n

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

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

第一点很少会带来麻烦。第二个,这就是您的示例中发生的情况,需要处理 \xe2\x80\x93 通常AVPlayer是第一个响应者并消耗空格键事件以及其他一些事件。为了使这项工作正常进行,您需要重写keyUp方法keyDown来将事件传播到响应者链上,就像默认NSView实现中发生的那样。

\n\n
// All player keyboard gestures are disabled.\noverride func keyDown(with event: NSEvent) {\n    self.nextResponder?.keyDown(with: event)\n}\n\n// All player keyboard gestures are disabled.\noverride func keyUp(with event: NSEvent) {\n    self.nextResponder?.keyUp(with: event)\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
override func responds(to selector: Selector!) -> Bool {\n    if selector == #selector(performClick(_:)) { return false }\n    return super.responds(to: selector)\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

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

如果您performKeyEquivalent在主菜单上调用而不检查结果,您可能最终会调用它两次 \xe2\x80\x93\xc2\xa0,第一次是手动,第二次是自动从实现中调用super,如果事件没有被响应者链。AVPlayer当第一响应者和keyDown方法keyUp未被覆盖时就会出现这种情况。

\n\n

PS Snippets 是 Swift 4,但想法是一样的!\xe2\x9c\x8c\xef\xb8\x8f

\n\n

PPS 有一篇精彩的WWDC 2010 会议 145 \xe2\x80\x93 Cocoa 应用程序中的关键事件处理,其中通过优秀的示例深入讨论了这个主题。Apple 开发者门户网站上不再列出 WWDC 2010-11,但可以在此处找到完整的会议列表。

\n


jus*_* k. 4

我有同样的问题。我没有仔细研究过,但据我所知,空格键“看起来”不像 Cocoa 的键盘快捷键,因此它被路由到-insertText:. 我的解决方案是对 NSWindow 进行子类化,在响应程序链上捕获它(大概您可以对 NSApp 进行子类化),然后将其显式发送到菜单系统:

- (void)insertText:(id)insertString
{
    if ([insertString isEqual:@" "]) {
        NSEvent *fakeEvent = [NSEvent keyEventWithType:NSKeyDown
                                              location:[self mouseLocationOutsideOfEventStream]
                                         modifierFlags:0
                                             timestamp:[[NSProcessInfo processInfo] systemUptime]
                                          windowNumber:self.windowNumber
                                               context:[NSGraphicsContext currentContext]
                                            characters:@" "
                           charactersIgnoringModifiers:@" "
                                             isARepeat:NO
                                               keyCode:49];
        [[NSApp mainMenu] performKeyEquivalent:fakeEvent];
    } else {
        [super insertText:insertString];
    }
}
Run Code Online (Sandbox Code Playgroud)