如何通过按 ESC 键关闭 window (NSWindowController)?

How to close window (NSWindowController) by hitting the ESC key?

问题

我希望用户能够通过按下 ESC 键关闭 window,但在这种特定情况下我无法让它工作,按下 ESC 会触发错误声音("no you can't do that" macOS bloop) 但没有任何反应。

上下文

我正在制作 NSWindowController 的子类,它本身创建 NSViewController 的子类的实例并将其设置在视图中。两个控制器都有自己的 xib 文件。

NSWindowController:

final class MyWindowController: NSWindowController, NSWindowDelegate {

    @IBOutlet weak var targetView: MainView!

    let myVC: MyViewController!
    var params: SomeParams!

    override func windowDidLoad() {
        super.windowDidLoad()
        myVC = MyViewController(someParams: params)
        myVC.view.setFrameSize(targetView.frame.size)
        myVC.view.setBoundsSize(targetView.bounds.size)
        targetView.addSubview(myVC.view)
    }

    override var windowNibName: String! {
        return "MyWindowController"
    }

    convenience init(someParams params: SomeType) {
        self.init(window: nil)
        self.params = params
    }

}

NSViewController:

final class MyViewController: NSViewController {

    convenience init(someParams params: SomeType) {
        // do stuff with the params
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // configure stuff for the window
    }

}

我试过的

假设 我的问题是 MyWindowController NSWindow 是 .initialFirstResponder 而我希望 targetView(一个 NSTableView)的内容成为第一响应者 - 这样我就可以使用 keyDown,我猜,然后从那里向 window 发送关闭命令。不过,这似乎不是最佳选择。

我尝试通过在 MyWindowController 的 windowDidLoad 中使用 window?.makeFirstResponder(theView) 强制视图控制器视图成为第一响应者,但没有任何改变。

我也试过将它添加到 MyWindowController:

override func cancelOperation(_ sender: Any?) {
    print("yeah, let's close!")
}

但这只适用于如果用户首先点击window的背景,然后点击ESC,它仍然会发出错误声音。这实际上让我认为问题出在 window.

上的第一响应者

问题

你将如何实现?当然,我知道用户已经可以用 CMD+W 关闭 window,但我还是很想解决这个问题。

请注意,代码示例在 Swift 中,但我也可以接受使用 Objective-C 的解释。

当我需要这样的行为时,我通过覆盖 NSWindow 对象的 keyDown: 来实现它。

即类似于以下内容:

- (void)keyDown:(NSEvent *)theEvent
{
    int k = [theEvent keyCode];
    if (k == kVK_Escape)
    {
        [self close];
        return;
    }

    [super keyDown:theEvent];
}

cancelOperation 的文档解释了 cancelOperation 的工作原理:

This method is bound to the Escape and Command-. (period) keys. The key window first searches the view hierarchy for a view whose key equivalent is Escape or Command-., whichever was entered. If none of these views handles the key equivalent, the window sends a default action message of cancelOperation: to the first responder and from there the message travels up the responder chain.

If no responder in the responder chain implements cancelOperation:, the key window searches the view hierarchy for a view whose key equivalent is Escape (note that this may be redundant if the original key equivalent was Escape). If no such responder is found, then a cancel: action message is sent to the first responder in the responder chain that implements it.

NSResponder declares but does not implement this method.

NSWindow 实现了 cancelOperation: 并且下一个响应者,即 window 控制器,没有检查 cancelOperation: 的实现。 cancel: 消息确实到达了 window 控制器。实施

- (void)cancel:(id)sender
{
    NSLog(@"cancel");
}

会起作用。 cancel: 消息不是从超类继承的,因此自动完成不会提示它。

这在 Xcode 10 和 Swift 4.2 中对我有用:

@objc func cancel(_ sender: Any?) {
    close()
}

我之前尝试过,但没有 @objc 部分,但没有成功。所以不要省略它。