9

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

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

我也尝试过 NSEventaddLocalMonitorForEventsMatchingMask:handler:addGlobalMonitorForEventsMatchingMask:handler:forNSFlagsChangedMask但是当主菜单打开时按下选项键时,本地或全局处理程序都不会被触发。

我怎样才能做到这一点?

4

5 回答 5

11

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

@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;
    }
}
于 2013-03-16T20:00:43.727 回答
10

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

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

此功能在此处的文档中进行了描述:管理备用

于 2013-12-19T22:22:13.767 回答
7

将以下内容添加到 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);
}

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

于 2012-09-08T19:44:59.447 回答
2

由于该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];
}
于 2013-10-16T06:26:42.473 回答
2

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

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

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

于 2018-01-28T04:07:57.650 回答