2012-06-26 21 views
6

Desidero aggiungere una voce di menu nel menu principale dell'applicazione che verrà utilizzata abbastanza rara. Voglio che sia nascosto di default e mostrarlo solo quando l'utente tiene premuto il tasto Opzione. Come faccio a fare questo?Nascondi/Mostra voce di menu nel menu principale dell'applicazione premendo il tasto Opzione

Sembra che devo gestire flagsChanged:, ma è il metodo NSResponder s' e NSMenu non eredita da NSResponder? L'ho provato all'interno del controller della finestra principale, e funziona quando premo il tasto Opzione prima di fare clic sul menu. Il seguente caso d'uso non funziona: clicca sulla voce di menu (non c'è nessun elemento), premi il tasto opzione - il mio oggetto dovrebbe apparire, il tasto opzione di rilascio - l'oggetto dovrebbe scomparire.

Ho anche provato NSEvent di addLocalMonitorForEventsMatchingMask:handler: e addGlobalMonitorForEventsMatchingMask:handler: per NSFlagsChangedMask ma quando tasto Opzione premuto mentre il menu principale è aperto né locale o gestori globali non sono licenziato.

Come posso fare questo?

risposta

5

Aggiungere quanto segue a 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]; 

quindi aggiungere

- (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); 
} 

il timer solo fuochi mentre i controlli vengono monitorati in modo da non è troppo di un calo di prestazioni.

+0

Ho appena avuto il tempo di provarlo e funziona. Molte grazie! –

9

Quando si costruisce il menu, includere l'elemento facoltativo e contrassegnarlo come nascosto. Quindi imposta l'istanza della classe come delegato del menu e aggiungi un osservatore del ciclo di esecuzione mentre il menu è aperto per controllare lo stato nascosto dell'oggetto opzionale.

@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

Questa sembra essere una soluzione migliore, ci proverò un po 'più tardi. Grazie. –

+0

Questo ha funzionato come un campione. – dbainbridge

+1

Attenzione qui. Ho appena provato questo e notato che quando si uno il menu e si sposta il mouse attraverso la barra dei menu per aprire altri menu, menuNeedsUpdate: non è sempre chiamato (se il menu era aperto prima), portando a un arresto anomalo. Usa 'menuWillOpen:' invece di 'menuNeedsUpdate:' – Mark

1

Dato che il metodo di NSMenuDelegatemenuNeedsUpdate: viene chiamato prima di visualizzazione, è possibile ignorare che, controllare se [NSEvent modifierFlags] ha il bit set alternativo, e l'uso che per mostrare/nascondere le voci di menu segreti.

Ecco un esempio, copiato da Reveal Functionality with Key Modifiers, che copre esattamente questo tema:

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

Non l'ho provato, ma sembra che questo metodo mostrerà la voce del menu segreto solo se il tasto Cmd è stato premuto prima di aprire il menu. Ma voglio mostrare/nascondere questa voce del menu premendo/rilasciando il tasto cmd mentre il menu è ancora aperto. –

10

Il modo migliore che si può raggiungere questo obiettivo è quello di utilizzare due voci di menu, la prima voce di menu utilizza una visualizzazione personalizzata di altezza 0 , ed è disabilitato, quindi proprio sotto è un elemento "alternativo". (Dovrai impostare l'articolo da keyEquivalentModifierMask a NSAlternateKeyMask) Con questa disposizione, quando premi il tasto di opzione, NSMenu sostituirà automaticamente la voce di menu a altezza zero con l'elemento alternativo che avrà l'effetto di far apparire magicamente una voce di menu.

Nessun bisogno di timer, aggiornamenti o notifiche di cambio bandiera.

Questa funzionalità è descritta nella documentazione qui: Managing Alternates

+0

"usa una vista personalizzata dell'altezza 0, ed è disabilitato" - Un esempio sarebbe carino. – frakman1

+0

Non è necessario utilizzare una vista personalizzata. Funziona (testato su macOS 11.6). –

+0

Questo potrebbe funzionare, ma è davvero dannoso per l'accessibilità e tutti gli altri meccanismi che determinano quali voci di menu sono disponibili per l'utente. Non vuoi nascondere dal sistema operativo lo stato del tuo NSMenuItem (in altre parole - nascondilo davvero). –

0

Ci sono alcune risposte complesse qui, ma in realtà è molto semplice:

creare 2 menuitems. Il primo è l'impostazione predefinita con qualsiasi chiave equivalente e titolo che si desidera. Il secondo è ciò che verrà mostrato quando il tasto modificatore è premuto - ancora una volta con chiave separataEquivalente e titolo. Sul secondo menu, abilita "Alternate" e tutto il resto avverrà automaticamente.

Il modificatore richiesto viene rilevato confrontando i 2 valori chiave equivalenti.