我的窗口中有一个NSTextField
,并且有4个菜单项,其等效键为← ↑ → ↓。
[当选择文本字段并按箭头键时,我希望光标在文本字段中移动,但会执行相应的菜单项操作。
因此,响应者链中必须有一个问题。为了弄清楚出了什么问题,我看过了WWDC 2010 Session 145 – Key Event Handling in Cocoa Applications线程中提到的NSMenuItem KeyEquivalent space " " bug。
会话中显示了键(热键)的事件流,如下所示:
因此,我检查了具有keyEquivalent = K
(只是任何普通键)的菜单项和具有keyEquivalent = →
(右箭头键)的菜单项的调用堆栈
First:K键事件调用堆栈;第二:向右箭头键事件调用堆栈
因此,按箭头键时,事件直接发送到mainMenu.performKeyEquivalent
,但实际上应该发送到keyWindow
吗?
为什么?如何解决此问题,以便我的NSTextField
在mainMenu
之前先收到箭头键事件?
关于调用栈差异的有趣观察。由于箭头键在导航中起着最重要的作用,因此它们的处理方式可能与其余键不同,就像您在NSMenuItem KeyEquivalent space " " bug线程中看到的那样。同样,在99.9%的情况下,AppKit会照顾好幕后的一切,使您的生活更轻松,这是其中一种情况。
您可以在文本框成为焦点时按k来查看实际的行为差异。与箭头不同,菜单项的等效键不会被触发,并且输入直接进入控件。
根据您的情况,您可以使用NSMenuItemValidation协议覆盖启用或禁用特定菜单项的默认操作。 AFAIK可将其放入链中的任何响应器中,例如视图控制器,窗口或应用程序。因此,当窗口的第一响应者是文本字段或使用这些事件正确操作的任何其他控件时,您可以在一个位置启用/禁用菜单项。
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需要检查项目的状态时,可以调用–从我的头上想不到任何东西。
P.S。在第一响应者上方,对NSTextView
而不是NSTextField
进行检查,为什么here's。
这是我选择的解决方案,来自@Willeke的评论。
我创建了NSWindow
的子类,并重写了keyDown(with:)
方法。我的应用程序中的每个窗口(当前为2个)都将此新NavigationWindow
子类化,以便可以在每个窗口中使用箭头键。
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
:向上箭头