2010-05-11 10 views
33

Ho un elemento barra di stato che apre un NSMenu, e ho un set delegato e è collegato correttamente (-(void)menuNeedsUpdate:(NSMenu *)menu funziona bene). Detto questo, il metodo è configurato per essere chiamato prima che venga visualizzato il menu, ho bisogno di ascoltarlo e di attivare una richiesta asincrona, in seguito di aggiornare il menu mentre è aperto, e non riesco a capire come dovrebbe essere fatto .In che modo Apple aggiorna il menu Airport mentre è aperto? (Come cambiare NSMenu quando è già aperto)

Grazie :)

EDIT

Ok, ora sono qui:

Quando si fa clic sulla voce di menu (nella barra di stato), un selettore si chiama che esegue un NSTask. Io uso il centro di notifica per ascoltare quando quel compito è finito, e scrivere:

[[NSRunLoop currentRunLoop] performSelector:@selector(updateTheMenu:) target:self argument:statusBarMenu order:0 modes:[NSArray arrayWithObject:NSEventTrackingRunLoopMode]]; 

e avere:

- (void)updateTheMenu:(NSMenu*)menu { 
    NSMenuItem *mitm = [[NSMenuItem alloc] init]; 
    [mitm setEnabled:NO]; 
    [mitm setTitle:@"Bananas"]; 
    [mitm setIndentationLevel:2]; 
    [menu insertItem:mitm atIndex:2]; 
    [mitm release]; 
} 

Questo metodo è sicuramente chiamato perché se scatto fuori il menu e immediatamente indietro su di esso, ottengo un menu aggiornato con queste informazioni in esso. Il problema è che non si sta aggiornando, mentre il menu è aperto.

risposta

13

Il problema qui è che è necessario il callback per ottenere innescato anche in modalità menù di monitoraggio.

Ad esempio, - [NSTask waitUntilExit] "esegue il polling del ciclo di esecuzione corrente utilizzando NSDefaultRunLoopMode fino al completamento dell'attività". Ciò significa che non verrà eseguito finché non si chiude il menu. A quel punto, la pianificazione updateTheMenu da eseguire su NSCommonRunLoopMode non aiuta, dopotutto non può tornare indietro nel tempo. Credo che gli osservatori di NSNotificationCenter si attivino anche in NSDefaultRunLoopMode.

Se riesci a trovare un modo per pianificare un callback che viene eseguito anche nella modalità di tracciamento dei menu, sei impostato; puoi semplicemente chiamare updateTheMenu direttamente da quel callback.

- (void)updateTheMenu { 
    static BOOL flip = NO; 
    NSMenu *filemenu = [[[NSApp mainMenu] itemAtIndex:1] submenu]; 
    if (flip) { 
    [filemenu removeItemAtIndex:[filemenu numberOfItems] - 1]; 
    } else { 
    [filemenu addItemWithTitle:@"Now you see me" action:nil keyEquivalent:@""]; 
    } 
    flip = !flip; 
} 

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 
    NSTimer *timer = [NSTimer timerWithTimeInterval:0.5 
              target:self 
             selector:@selector(updateTheMenu) 
             userInfo:nil 
              repeats:YES]; 
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 
} 

Esegui questa operazione e tieni premuto il menu File, e vedrai apparire la voce di menu in più e scompare ogni mezzo secondo. Ovviamente "ogni mezzo secondo" non è quello che stai cercando, e NSTimer non capisce "quando il mio compito in background è finito". Ma ci può essere un meccanismo altrettanto semplice che puoi usare.

In caso contrario, è possibile crearlo da una sottoclasse NSPort, ad esempio, creare un NSMessagePort e fare in modo che NSTask lo scriva al termine.

L'unico caso in cui hai davvero bisogno di pianificare in modo esplicito updateTheMenu come descritto in precedenza da Rob Keniger è se stai cercando di chiamarlo al di fuori del ciclo di esecuzione. Ad esempio, è possibile generare un thread che avvia un processo secondario e chiama waitpid (che blocca fino al termine del processo), quindi quel thread dovrebbe chiamare performSelector: target: argomento: order: modes: invece di chiamare updateTheMenu direttamente.

+1

Nonostante il lungo tempo trascorso dalla domanda, sono ancora curioso di questo e sono così felice di vedere perché quello che avevo non funzionava. Grazie! – Aaron

13

(Se si desidera modificare il layout del menu, in modo simile a come il menu Airport mostra più informazioni quando si fa clic sull'opzione, continuare a leggere. Se si desidera fare qualcosa di completamente diverso, allora questa risposta potrebbe non essere pertinente come desideri.)

La chiave è -[NSMenuItem setAlternate:]. Ad esempio, supponiamo di costruire un NSMenu con un'azione Do something.... Faresti codice che come qualcosa di simile:

NSMenu * m = [[NSMenu alloc] init]; 

NSMenuItem * doSomethingPrompt = [m addItemWithTitle:@"Do something..." action:@selector(doSomethingPrompt:) keyEquivalent:@"d"]; 
[doSomethingPrompt setTarget:self]; 
[doSomethingPrompt setKeyEquivalentModifierMask:NSShiftKeyMask]; 

NSMenuItem * doSomething = [m addItemWithTitle:@"Do something" action:@selector(doSomething:) keyEquivalent:@"d"]; 
[doSomething setTarget:self]; 
[doSomething setKeyEquivalentModifierMask:(NSShiftKeyMask | NSAlternateKeyMask)]; 
[doSomething setAlternate:YES]; 

//do something with m 

Ora, si potrebbe pensare che questo sarebbe creare un menu con due voci in essa: "Fare qualcosa ..." e "fare qualcosa", e si Avrei in parte ragione. Poiché impostiamo la seconda voce di menu come alternativa e poiché entrambe le voci di menu hanno lo stesso equivalente chiave (ma diverse maschere di modifica), verrà mostrato solo il primo (ovvero, quello predefinito per setAlternate:NO). Quindi, quando si apre il menu, se si preme la maschera di modifica che rappresenta la seconda (cioè la chiave di opzione), la voce di menu si trasformerà in tempo reale dalla prima voce di menu alla seconda.

Questo, ad esempio, è come funziona il menu Apple. Se fai clic una volta su di esso, vedrai alcune opzioni con le ellissi che seguono, come "Riavvia ..." e "Spegni ...". HIG specifica che se è presente un puntino di sospensione, significa che il sistema chiederà conferma all'utente prima di eseguire l'azione. Tuttavia, se si preme il tasto di opzione (con il menu ancora aperto), si noterà che cambiano in "Riavvia" e "Spegni". Gli ellissi scompaiono, il che significa che se li selezioni mentre il tasto opzione viene premuto, verranno eseguiti immediatamente senza chiedere conferma all'utente.

La stessa funzionalità generale è valida per i menu nelle voci di stato. Puoi avere le informazioni espanse come voci "alternative" alle informazioni regolari che vengono visualizzate solo quando viene premuto il tasto opzione. Una volta compreso il principio di base, è in realtà abbastanza semplice da implementare senza un sacco di trucchi.

+4

Informazioni estremamente utili, anche se non per quello che volevo. Troverò sicuramente un uso per questo però. Sono davvero dopo il modo in cui il menu inietta le reti che trova mentre è aperto. Grazie – Aaron

16

Il tracciamento del mouse del menu viene eseguito in una modalità di ciclo di esecuzione speciale (NSEventTrackingRunLoopMode).Per modificare il menu, è necessario inviare un messaggio in modo che venga elaborato nella modalità di tracciamento degli eventi. Il modo più semplice per farlo è quello di utilizzare questo metodo di NSRunLoop:

[[NSRunLoop currentRunLoop] performSelector:@selector(updateTheMenu:) target:self argument:yourMenu order:0 modes:[NSArray arrayWithObject:NSEventTrackingRunLoopMode]] 

È inoltre possibile specificare la modalità di come NSRunLoopCommonModes e il messaggio verrà inviato durante una qualsiasi delle modalità del ciclo di esecuzione comuni, tra cui NSEventTrackingRunLoopMode.

tuo metodo di aggiornamento sarebbe poi fare qualcosa di simile:

- (void)updateTheMenu:(NSMenu*)menu 
{ 
    [menu addItemWithTitle:@"Foobar" action:NULL keyEquivalent:@""]; 
    [menu update]; 
} 
+0

Stasera dai un colpo, grazie! – Aaron

+0

Questo non sembra funzionare. Faccio la mia richiesta di dati tramite NSTask, attendo una notifica, al ricevimento di tale notifica, popolino un oggetto dati accessibile all'intera classe e chiamo la tua linea NSRunLoop che chiama un metodo updateTheMenu. Tuttavia, il menu non viene aggiornato dal vivo, devo fare clic su di esso e quindi riaprirlo prima che vengano visualizzate le informazioni aggiornate. – Aaron

+1

Se si utilizza 'NSRunLoopCommonModes' invece di' NSEventTrackingRunLoopMode' funziona allora? –

Problemi correlati