Cocoa 箭头键的应用程序响应程序链

Cocoa 箭头键的应用程序响应程序链,cocoa,first-responder,nsmenuitem,nsapplication,nsresponder,Cocoa,First Responder,Nsmenuitem,Nsapplication,Nsresponder,我的窗口中有一个NSTextField,有4个菜单项和对应的键←↑→↓. 当选择文本字段并按下箭头键时,我希望光标在文本字段中移动,但会执行相应的菜单项操作 因此,在响应者链中必须存在一个问题。为了找出我在这篇文章中提到的问题 按键(热键)的事件流在会话中显示如下: 因此,我用一个菜单项检查了调用堆栈,该菜单项具有keyEquivalent=K(只是任何普通键)和一个菜单项具有keyEquivalent=→(右箭头键) 第一:K键事件调用堆栈;第二:右箭头键事件调用堆栈 因此,当按下

我的窗口中有一个
NSTextField
,有4个菜单项和对应的键←↑→↓.

当选择文本字段并按下箭头键时,我希望光标在文本字段中移动,但会执行相应的菜单项操作

因此,在响应者链中必须存在一个问题。为了找出我在这篇文章中提到的问题


按键(热键)的事件流在会话中显示如下:



因此,我用一个菜单项检查了调用堆栈,该菜单项具有
keyEquivalent=K
(只是任何普通键)和一个菜单项具有
keyEquivalent=→(右箭头键)

第一:K键事件调用堆栈;第二:右箭头键事件调用堆栈

因此,当按下箭头键时,事件将直接发送到
主菜单。performKeyEquivalent
,但它实际上应该发送到
键窗口
,对吗


这是为什么?我如何修复此行为,使我的
NSTextField
主菜单
执行之前接收箭头键事件?

关于调用堆栈差异的有趣观察。由于箭头键在导航中起着最重要的作用,它们的处理方式可能与其他键不同,正如您在线程中看到的。同样,AppKit在99.9%的情况下会处理好幕后的一切,让您的生活更轻松,这也是其中之一

当文本字段具有焦点时,按k键可以看到行为的实际差异。与箭头不同,菜单项的等价键不会被触发,输入直接进入控件

对于您的情况,您可以使用协议覆盖启用或禁用特定菜单项的默认操作。AFAIK这可以进入链中的任何响应程序,例如视图控制器、窗口或应用程序。因此,当窗口的第一个响应程序是textfield或使用这些事件正常运行的任何其他控件时,可以在单个位置启用/禁用菜单项

extension ViewController: NSMenuItemValidation {
    func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
        // Filter menu item by it's assigned action, just as an exampe.
        if menuItem.action != #selector(ViewController.menuActionLeftArrowKey(_:)) { return true }
        Swift.print("Validating menu item:", menuItem)
        // Disable the menu item if first responder is text view.
        let isTextView = self.view.window?.firstResponder is NSTextView
        return !isTextView
    }
}
这将在显示菜单以更新项目状态之前被调用,在调用菜单项键以检查操作是否需要发送之前被调用,并且在其他情况下,当AppKit需要检查项目状态时可能会被调用——我想不出有什么


如上所述,第一响应者检查是针对
NSTextView
而不是
NSTextField
,原因。

这是我选择的解决方案,它是由@Willeke的评论得出的

我已经创建了
NSWindow
的子类,并覆盖了
keyDown(with:)
方法。我的应用程序中的每个窗口(当前为2个)都将这个新的
导航窗口子类化,以便您可以在每个窗口中使用箭头键

class NavigationWindow: NSWindow {

    override func keyDown(with event: NSEvent) {
        if event.keyCode == 123 || event.keyCode == 126 || event.specialKey == NSEvent.SpecialKey.pageUp {
            print("navigate back")
        } else if event.keyCode == 124 || event.keyCode == 125 || event.specialKey == NSEvent.SpecialKey.pageDown {
            print("navigate forward")
        } else {
            super.keyDown(with: event)
        }
    }
}
此实现注册所有四个箭头键以及页面上下键以进行导航

这些是关键代码

  • 123
    :右箭头
  • 124
    :左箭头
  • 125
    :向下箭头
  • 126
    :向上箭头

看。@Willeke我已经看过了,但我仍然不明白为什么会有这种行为。在图中显示,如果它是一个键等价物,那么它将被发送到键窗口视图层次结构。这种情况发生在P快捷方式上,但不发生在箭头上。根据图表,箭头应该被发送给第一响应者,但这并没有发生。可能箭头键最初被视为等同于键,而没有命令键修饰符的P则不是。调用堆栈不是事件所采用的路由。请参阅。注意:现在视图控制器也在响应器链中。如果不处理按键,请调用
super.keyDown(with:event)
。非常感谢您的回答!这是一个有趣的解决方案。但不幸的是,我无法在我的应用程序中应用此功能,因为用户需要能够在编辑时选择菜单项…很好!您可能需要使用关键常量,如kVK_LeftArrow、kVK_RightArrow等。大多数都可以看到。