Скрыть/показать пункт меню в главном меню приложения, нажав клавишу Option

Я хочу добавить пункт меню в Главное меню приложения, который будет использоваться довольно редко. Я хочу, чтобы он был скрыт по умолчанию и показывался только тогда, когда пользователь удерживает клавишу Option. Как мне это сделать?

кажется, что я должен обрабатывать flagsChanged:, но это и NSMenu не наследуется от NSResponder? Я попробовал его внутри контроллера главного окна, и он работает, когда я нажимаю клавишу Option, прежде чем нажать на меню. Следующий вариант использования doe не работает: нажмите на пункт меню (там нет элемента), нажмите клавишу option - мой элемент должен появиться, отпустите клавишу option-элемент должен исчезнуть.

Я также пытался NSEvent это addLocalMonitorForEventsMatchingMask:handler: и addGlobalMonitorForEventsMatchingMask:handler: на NSFlagsChangedMask но при нажатии клавиши option во время открытия главного меню ни локальные, ни глобальные обработчики не запускаются.

как я могу это сделать?

5 ответов


добавьте следующее в 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);
}

таймер срабатывает только во время отслеживания элементов управления, поэтому это не слишком большой удар по производительности.


при построении меню включите необязательный элемент и отметьте его как скрытый. Затем установите экземпляр класса в качестве делегата меню и добавьте наблюдателя цикла выполнения, пока меню открыто для управления скрытым состоянием необязательного элемента.

@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;
    }
}

лучший способ достичь этого-использовать два пункта меню, Первый пункт меню использует пользовательский вид высоты 0 и отключен, а затем прямо под ним находится "альтернативный" элемент. (Вам нужно будет установить keyEquivalentModifierMask до NSAlternateKeyMask) при таком расположении, когда вы нажимаете клавишу option, NSMenu автоматически заменит пункт меню нулевой высоты альтернативным пунктом, который будет иметь эффект волшебного появления пункта меню.

не нужны таймеры, обновления или флаг уведомление об изменении.

эта функция описана в документации здесь: Управление Поперся


С NSMenuDelegate метод menuNeedsUpdate: вызывается перед отображением, его можно переопределить, проверить, если [NSEvent modifierFlags] имеет альтернативный бит, и используйте его, чтобы показать/скрыть ваши секретные пункты меню.

вот пример, скопированный из показать функциональность с ключевыми модификаторами, который охватывает именно эту тему:

#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];
}

здесь есть некоторые сложные ответы, но на самом деле это очень просто:

создать 2 меню. Первый-по умолчанию с любым keyEquivalent и заголовком, который вы хотите. Второй - это то, что будет показано, когда клавиша - модификатор снова будет отключена с отдельным keyEquivalent и title. Во втором меню включите "Alternate", и все остальное произойдет автоматически.

требуемый модификатор обнаруживается путем сравнения 2 значений keyEquivalent.