按Option键,在应用程序主菜单中隐藏/显示菜单项

时间:2012-06-26 13:41:34

标签: cocoa nsmenu

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

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

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

我该怎么做?

5 个答案:

答案 0 :(得分: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;
    }
}

答案 1 :(得分:11)

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

无需定时器,更新或标记更改通知。

此功能在此处的文档中进行了描述:Managing Alternates

答案 2 :(得分: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);
}

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

答案 3 :(得分: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];
}

答案 4 :(得分:0)

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

创建2个菜单项。第一个是默认值,包含你想要的keyEquivalent和title。第二个是修饰键关闭时显示的内容 - 再次使用单独的keyEquivalent和title。在第二个菜单项上,启用' Alternate'其他一切都会自动发生。

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