【问题标题】:Hide/Show menu item in application's main menu by pressing Option key按 Option 键隐藏/显示应用程序主菜单中的菜单项
【发布时间】:2012-06-26 13:41:34
【问题描述】:

我想在应用程序的主菜单中添加一个很少使用的菜单项。我希望它默认隐藏并仅在用户按住 Option 键时显示。我该怎么做?

看来我应该处理flagsChanged:,但这是NSResponder的方法,NSMenu不继承自NSResponder?我在主窗口控制器中尝试过它,当我在单击菜单之前按 Option 键时它可以工作。以下用例不起作用:单击菜单项(没有项目),按选项键 - 我的项目应该出现,释放选项键 - 项目应该消失。

我还尝试了 NSEvent 的 addLocalMonitorForEventsMatchingMask:handler:addGlobalMonitorForEventsMatchingMask:handler: for NSFlagsChangedMask,但是当主菜单打开时按下选项键时,不会触发本地或全局处理程序。

我该怎么做?

【问题讨论】:

    标签: cocoa nsmenu


    【解决方案1】:

    在构建菜单时包含可选项目并将其标记为隐藏。然后将你的类实例设置为菜单的委托,并在菜单打开时添加一个运行循环观察者来控制可选项目的隐藏状态。

    @implementation AppController {
        CFRunLoopObserverRef _menuObserver;
    }
    
    - (void)updateMenu {
        BOOL hideOptionalMenuItems = ([NSEvent modifierFlags] & NSAlternateKeyMask) != NSAlternateKeyMask;
        [self.optionalMenuItem setHidden:hideOptionalMenuItems];
    }
    
    - (void)menuWillOpen:(NSMenu *)menu {
        [self updateMenu];
    
        if (_menuObserver == NULL) {
            _menuObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
                [self updateMenu];
            });
    
            CFRunLoopAddObserver(CFRunLoopGetCurrent(), _menuObserver, kCFRunLoopCommonModes);
        }
    }
    
    - (void)menuDidClose:(NSMenu *)menu {
        if (_menuObserver != NULL) {
            CFRunLoopObserverInvalidate(_menuObserver);
            CFRelease(_menuObserver);
            _menuObserver = NULL;
        }
    }
    

    【讨论】:

    • 这似乎是一个更好的解决方案,我稍后会尝试一下。谢谢。
    • 这里要小心。我刚刚尝试过这个,并注意到当您选择一个菜单并将鼠标移过菜单栏以打开其他菜单时,menuNeedsUpdate: 并不总是被调用(如果菜单之前打开过),这会导致崩溃。使用menuWillOpen: 而不是menuNeedsUpdate:
    • 很好,我已经更新了答案。我认为 Psycho 的解决方案是要走的路,因为它可以完全在 Interface Builder 中完成。我从来没有想过尝试零高度视图,这很棒。
    【解决方案2】:

    实现这一点的最佳方法是使用两个菜单项,第一个菜单项使用高度为 0 的自定义视图,并且被禁用,然后在它的正下方是一个“替代”项。 (您必须将此项的keyEquivalentModifierMask 设置为NSAlternateKeyMask)通过这种安排,当您按下选项键时,NSMenu 会自动将零高度的菜单项替换为具有制作菜单效果的备用项项目神奇地出现。

    不需要计时器、更新或标志更改通知。

    这里的文档中描述了此功能:Managing Alternates

    【讨论】:

    • "使用高度为 0 的自定义视图,并且被禁用" - 举个例子就好了。
    • 无需使用自定义视图。它可以正常工作(在 macOS 11.6 上测试)。
    • 这可能有效,但在可访问性和所有其他检查用户可用菜单项的机制方面确实很糟糕。您不想向操作系统隐藏 NSMenuItem 的状态(换句话说 - 真正隐藏它)。
    【解决方案3】:

    将以下内容添加到 applicationDidFinishLaunching。

    // Dynamically update QCServer menu when option key is pressed
    NSMenu *submenu = [[[NSApp mainMenu] itemWithTitle:@"QCServer"] submenu];    
    NSTimer *t = [NSTimer timerWithTimeInterval:0.1 target:self selector:@selector(updateMenu:) userInfo:submenu repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:t forMode:NSEventTrackingRunLoopMode];
    

    然后添加

    - (void)updateMenu:(NSTimer *)t {
    
        static NSMenuItem *menuItem = nil;
        static BOOL isShowing = YES;
    
        // Get global modifier key flag, [[NSApp currentEvent] modifierFlags] doesn't update while menus are down
        CGEventRef event = CGEventCreate (NULL);
        CGEventFlags flags = CGEventGetFlags (event);
        BOOL optionKeyIsPressed = (flags & kCGEventFlagMaskAlternate) == kCGEventFlagMaskAlternate;
        CFRelease(event);
    
        NSMenu *menu = [t userInfo];
    
        if (!menuItem) {
            // View Batch Jobs...
             menuItem = [menu itemAtIndex:6];
            [menuItem retain];
        }
    
        if (!isShowing && optionKeyIsPressed) {
            [menu insertItem:menuItem atIndex:6];
            [menuItem setEnabled:YES];
            isShowing = YES;
        } else if (isShowing && !optionKeyIsPressed) {
            [menu removeItem:menuItem];
            isShowing = NO;
        }
    
        NSLog(@"optionKeyIsPressed %d", optionKeyIsPressed);
    }
    

    计时器仅在跟踪控件时触发,因此不会对性能造成太大影响。

    【讨论】:

      【解决方案4】:

      由于NSMenuDelegate 方法menuNeedsUpdate: 在显示之前被调用,因此可以覆盖它,检查[NSEvent modifierFlags] 是否设置了备用位,并使用它来显示/隐藏您的秘密菜单项。

      这是一个示例,复制自 Reveal Functionality with Key Modifiers,它完全涵盖了这个主题:

      #pragma NSMenu delegate methods
      
      - (void) menuNeedsUpdate: (NSMenu *)menu
      {
          NSLog(@"menuNeedsUpdate: %@", menu);
      
          NSUInteger flags = ([NSEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask);
      
          // We negate the value below because, if flags == NSAlternateKeyMask is TRUE, that
          // means the user has the Option key held, and wants to see the secret menu, so we
          // need shoudHideSecretMenu to be FALSE, so we just negate the value. 
          BOOL shoudHideSecretMenu = !(flags == NSAlternateKeyMask);
      
          NSLog(@"Flags: 0x%lx (0x%x), shoudHideSecretMenu = %d", flags, NSAlternateKeyMask, shoudHideSecretMenu);
      
          [secretMenuItem setHidden:shoudHideSecretMenu];
      }
      

      【讨论】:

      • 我没有尝试过,但似乎只有在打开菜单之前按下 Cmd 键时,此方法才会显示秘密菜单项。但是我想在菜单仍然打开时通过按/释放 cmd 键来显示/隐藏此菜单项。
      【解决方案5】:

      这里有一些复杂的答案,但实际上很简单:

      创建 2 个菜单项。第一个是您想要的任何 keyEquivalent 和标题的默认值。第二个是当修改键按下时将显示的内容 - 再次使用单独的 keyEquivalent 和标题。在第二个菜单项上,启用“Alternate”,其他一切都会自动发生。

      通过比较两个 keyEquivalent 值来检测所需的修饰符。

      【讨论】:

      • 这个 answer 缺少一些 Apple 文档,其中包含一些有用的代码 sn-ps。
      猜你喜欢
      • 1970-01-01
      • 2014-09-22
      • 2011-09-22
      • 1970-01-01
      • 2015-09-08
      • 1970-01-01
      • 1970-01-01
      • 2021-06-08
      • 2017-02-26
      相关资源
      最近更新 更多