NSMenuItem KeyEquivalent" "(空格)错误

时间:2012-06-22 11:22:24

标签: cocoa keyboard-shortcuts nsmenuitem

我想在没有NSMenuItem的任何修饰符的情况下设置键等效“”(空格)(在应用程序主菜单中)。

如文件所示:

  

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

     

[menuItem setKeyEquivalent:@“”];

     

[menuItem setKeyEquivalentModifierMask:0];

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

我需要使用没有修饰符的“Space”。请帮忙!

4 个答案:

答案 0 :(得分: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];
    }
}

答案 1 :(得分:1)

我刚刚遇到了同样的问题......

当我的应用中空格键等效工作正常,而NSMenuItem的链接IBAction位于App Delegate中。

如果我将IBAction移动到专用控制器中,则会失败。所有其他菜单项等效键继续工作但空格键没有响应(使用修饰键可以,但未修改的@“”将无效)。

我尝试了各种解决方法,例如直接链接到控制器与通过响应链链接,但无济于事。我尝试了代码方式:

[menuItem setKeyEquivalent:@" "];  
[menuItem setKeyEquivalentModifierMask:0];  

和Interface Builder方式,行为是一样的

根据Justin的回答,我已经尝试过对NSWindow进行子类化,但到目前为止还未能实现这一点。

所以现在我已经将这一个IBAction投降并重新安置到它所在的App Delegate。我不认为这是一个解决方案,只是做了...也许这是一个错误,或者(更有可能)我只是不了解事件消息和响应者链。

答案 2 :(得分:1)

这是一个棘手的问题。就像许多答案所暗示的那样,在应用程序或窗口级别拦截事件是强制菜单项起作用的可靠方法。同时,它可能会破坏其他内容,例如,如果您专注于NSTextFieldNSButton,则希望他们使用事件,而不是菜单项。如果用户在系统偏好设置中为该菜单项重新定义了等效的键,即将Space更改为P,则这也可能会失败。

使用与菜单项等效的空格键的事实使事情变得更加棘手。空格键是UI特殊的事件字符之一,另外还有箭头键和其他一些字符,AppKit对此有不同的对待,在某些情况下,它们会传播到主菜单之前被消耗掉。

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

  1. NSApplication.sendEvent将事件发送到键窗口。
  2. “键”窗口在NSWindow.sendEvent中接收事件,确定它是否为键事件,并自行调用performKeyEquivalent
  3. performKeyEquivalent将其发送到当前窗口的firstResponder
  4. 如果响应者不使用它,则将事件向上递归发送到nextResponder
  5. performKeyEquivalent如果其中一个响应者使用了事件,则返回true,否则返回false

现在,第二个棘手的问题是,如果事件没有被使用(即performKeyEquivalent返回false时),window will try to process it as a special keyboard UI event – {{3中已简要提及}}:

  

Cocoa事件分发体系结构将某些关键事件视为命令,以将控件焦点移至窗口中的其他用户界面对象,模拟鼠标单击对象,关闭模式窗口以及在对象中进行选择允许选择。此功能称为键盘界面控制。键盘界面控件中涉及的大多数用户界面对象都是NSControl对象,但不是控件的对象也可以参与。

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

  1. Cocoa Event Handling Guide窗口(选择器)。
  2. 它会与第一响应者进行检查,是否respondsToSelector并调用它。
  3. 如果调用了该操作,则该事件将被视为已消耗,并且事件传播将停止。

因此,请牢记所有这些,确保两件事:

  1. 正确设置了响应者链。
  2. 响应者只消耗他们需要的东西,否则传播事件。

第一点很少带来麻烦。第二个,这就是您的示例中发生的情况,需要注意– AVPlayer通常是第一个响应者,并使用空格键事件以及其他一些事件。为此,您需要覆盖keyUpkeyDown方法,以将事件传播到响应者链,就像在默认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继承对象,它将使用该事件。通常,您确实希望发生这种情况,但是如果不希望发生这种情况,例如在实现自定义控件时,您可以覆盖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是第一响应者,并且keyDownkeyUp方法没有被覆盖时,情况就是如此。

P.S。片段是Swift 4,但是想法是一样的! ✌️

答案 3 :(得分:0)

快速Swift 4-5方法:

在视图控制器中:

// Capture space and call main menu
override func keyDown(with event: NSEvent) {
    if event.keyCode == 49 && !event.isARepeat{
        NSApp.mainMenu?.performKeyEquivalent(with: event)
    }
    super.keyDown(with: event)
}