如何创建一个包含仅在按住键盘修改键时出现的 NSMenuItem 的 NSMenu?

How to create an NSMenu containing an NSMenuItem which only appears while holding a keyboard modifier key?

我想创建一个 NSMenu,其中包含默认隐藏的 NSMenuItem,并且仅在用户按住键盘修改键时出现。

基本上,我正在寻找与 Finder 'Go' 菜单中的 'Library' 选项相同的行为:

不持有 Option (⌥):

按住 Option (⌥):


我已经尝试使用 [NSEvent addGlobalMonitorForEventsMatchingMask: handler:] 安装密钥侦听器,通过将 NSMenuItem 设置为 hidden 属性 以编程方式隐藏和取消隐藏 NSMenuItem。这种方法可行,但问题是 hiding/unhiding 在 NSMenu 打开时不起作用。显然,NSMenu 在打开时完全接管了事件处理循环,从而阻止了关键侦听器的工作。
我可能可以使用 CGEventTap 在 NSMenu 打开时仍然接收事件,但这似乎完全矫枉过正。

我发现的另一个与我想要的类似的东西是 NSMenu 的“alternate”机制。但我只能让它切换 NSMenuItems,而不是 hide/unhide 它们。

如有任何帮助,我们将不胜感激。谢谢!

假设您的仅选项菜单项的操作是(在 Swift 中)performOptionOnlyMenuItem(_:),其目标是您的 AppDelegate

  • 您需要做的第一件事是确保 AppDelegate 符合 NSMenuItemValidation 协议。

  • 您需要做的第二件事是实现 validateMenuItem(_:) 方法,并让它检查菜单项是否发送 performOptionOnlyMenuItem(_:) 动作。如果是,则根据当前是否按下选项键设置项目的isHidden 属性。

如果您不需要验证任何其他菜单项,代码可以如下所示:

extension AppDelegate: NSMenuItemValidation {
    func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
        switch menuItem.action {
        case #selector(performOptionOnlyMenuItem(_:)):
            let flags = NSApp.currentEvent?.modifierFlags ?? []
            menuItem.isHidden = !flags.contains(.option)
            return true
        default:
            return true
        }
    }
}

如果操作被发送到其他目标,您需要在该目标上实施验证(包括协议一致性)。每个菜单项仅由项目的目标验证。

我找到了一个完美的解决方案!

  1. 在你想要隐藏的NSMenuItem上,将alternate属性设置为YES,然后设置keyEquivalentModifierMask属性 到您想要 取消隐藏 项目的键盘修饰符。

  2. 在您要隐藏的 NSMenu 之前 NSMenuItem,插入另一个 NSMenuItem高度为 0.

    在 Objc 中,您可以像这样创建高度为 0 的 NSMenuItem

    NSMenuItem *i = [[NSMenuItem alloc] init];
    i.view = [[NSView alloc] initWithFrame:NSZeroRect];
    

可隐藏的 NSMenuItem 现在将 'alternate' 到它前面的零高度 NSMenuItem。零高度项目将默认显示,但当您按住指定的键盘修饰符时,零高度项目将与可隐藏项目交换。因为零高度项目是不可见的,所以这具有取消隐藏可隐藏项目的效果。