2010-05-11 17 views
33

J'ai un élément de la barre d'état qui ouvre un NSMenu, et j'ai un ensemble de délégués et il est branché correctement (-(void)menuNeedsUpdate:(NSMenu *)menu fonctionne très bien). Cela dit, cette méthode est configurée pour être appelée avant que le menu ne soit affiché, je dois l'écouter et déclencher une requête asynchrone, mettre à jour le menu pendant qu'il est ouvert, et je n'arrive pas à comprendre comment cela est censé être fait .Comment Apple met-il à jour le menu Airport lorsqu'il est ouvert? (Comment modifier NSMenu quand il est déjà ouvert)

Merci :)

EDIT

Ok, je suis maintenant ici:

Lorsque vous cliquez sur l'élément de menu (dans la barre d'état), un sélecteur est appelé qui exécute un NSTask. J'utiliser le centre de notification pour écouter quand cette tâche est terminée, et écrire:

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

et ont:

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

Cette méthode est certainement appelé parce que si je clique sur le menu et immédiatement en arrière dessus, je reçois un menu mis à jour avec cette information en elle. Le problème est qu'il ne se met pas à jour (le menu est ouvert).

Répondre

13

Le problème ici est que vous avez besoin de votre rappel pour se déclencher même en mode de suivi du menu. Par exemple, - [NSTask waitUntilExit] "interroge la boucle d'exécution en cours en utilisant NSDefaultRunLoopMode jusqu'à la fin de la tâche". Cela signifie qu'il ne sera pas exécuté avant la fermeture du menu. À ce stade, la programmation de updateTheMenu pour s'exécuter sur NSCommonRunLoopMode n'aide pas: elle ne peut pas remonter dans le temps, après tout. Je crois que les observateurs de NSNotificationCenter déclenchent également seulement dans NSDefaultRunLoopMode.

Si vous pouvez trouver un moyen de planifier un rappel qui s'exécute même en mode de suivi de menu, vous êtes défini; vous pouvez simplement appeler updateTheMenu directement à partir de ce rappel. Exécutez ceci et maintenez le menu Fichier enfoncé, et vous verrez l'élément de menu supplémentaire apparaître et disparaître toutes les demi-secondes. Évidemment, "chaque demi-seconde" n'est pas ce que vous cherchez, et NSTimer ne comprend pas "quand ma tâche d'arrière-plan est terminée". Mais il peut y avoir un mécanisme tout aussi simple que vous pouvez utiliser. Si ce n'est pas le cas, vous pouvez le créer vous-même à partir de l'une des sous-classes NSPort, par exemple, créez un NSMessagePort et demandez à votre NSTask d'écrire dessus quand c'est fait. Le seul cas où vous allez vraiment avoir besoin de planifier explicitement updateTheMenu comme Rob Keniger décrit ci-dessus est si vous essayez de l'appeler depuis l'extérieur de la boucle d'exécution. Par exemple, vous pouvez générer un thread qui déclenche un processus fils et appelle waitpid (qui bloque jusqu'à ce que le processus soit terminé), puis ce thread doit appeler performSelector: target: argument: order: modes: au lieu d'appeler updateTheMenu directement.

+1

Malgré le temps qui s'est écoulé depuis la question, j'ai toujours été curieux à ce sujet et je suis si heureux de voir pourquoi ce que j'avais ne fonctionnait pas. Je vous remercie! – Aaron

13

(Si vous souhaitez modifier la disposition du menu, similaire à la façon dont le menu Airport affiche plus d'informations lorsque vous cliquez dessus, continuez à lire.) Si vous voulez faire quelque chose de complètement différent, cette réponse peut ne pas être si pertinent que vous le souhaitez.)

La clé est -[NSMenuItem setAlternate:]. Pour un exemple, disons que nous allons construire un NSMenu qui a une action Do something... dedans. Vous auriez du code qui comme quelque chose comme:

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 

Maintenant, vous pourriez penser que cela créerait un menu avec deux éléments qu'il contient: « Faites quelque chose ... » et « faire quelque chose », et vous Je serais en partie juste. Étant donné que nous définissons le second élément de menu comme étant alternatif et que les deux éléments de menu ont le même équivalent clé (mais des masques de modification différents), seul le premier (par défaut setAlternate:NO) s'affiche. Ensuite, lorsque vous avez ouvert le menu, si vous appuyez sur le masque modificateur qui représente le second (c'est-à-dire la touche d'option), alors l'élément de menu se transformera en temps réel du premier élément de menu à la seconde.

C'est par exemple le fonctionnement du menu Apple. Si vous cliquez une fois dessus, vous verrez quelques options avec des ellipses après, comme "Redémarrer ..." et "Arrêter ...". Le HIG spécifie que s'il y a une ellipse, cela signifie que le système demandera à l'utilisateur une confirmation avant d'exécuter l'action. Cependant, si vous appuyez sur la touche d'option (avec le menu toujours ouvert), vous remarquerez qu'ils passent à "Redémarrer" et "Arrêter". Les ellipses disparaissent, ce qui signifie que si vous les sélectionnez alors que la touche d'option est enfoncée, elles s'exécuteront immédiatement sans demander confirmation à l'utilisateur.

La même fonctionnalité générale est vraie pour les menus dans les éléments d'état. Vous pouvez avoir les informations étendues être des éléments «alternatifs» à l'information régulière qui n'apparaît que lorsque la touche d'option est enfoncée. Une fois que vous comprenez le principe de base, il est en fait assez facile à mettre en œuvre sans beaucoup de tromperie.

+4

Informations extrêmement utiles, mais pas ce que je voulais. Je vais certainement trouver un usage pour cela. Je suis vraiment après la façon dont le menu injecte les réseaux qu'il trouve lorsqu'il est ouvert. Merci – Aaron

16

Le suivi de la souris du menu est effectué dans un mode de boucle d'exécution spéciale (NSEventTrackingRunLoopMode).Pour modifier le menu, vous devez envoyer un message afin qu'il soit traité dans le mode de suivi des événements. La meilleure façon de le faire est d'utiliser cette méthode de NSRunLoop:

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

Vous pouvez également spécifier le mode comme NSRunLoopCommonModes et le message sera envoyé au cours d'une des communes modes de boucle d'exécution, y compris NSEventTrackingRunLoopMode.

Votre méthode de mise à jour alors faire quelque chose comme ceci:

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

Je vais essayer ce soir, merci! – Aaron

+0

Cela ne semble pas fonctionner. Je fais ma demande de données via NSTask, attend une notification, à la réception de cette notification, je remplis un objet de données accessible à toute la classe, et appelle votre ligne NSRunLoop qui appelle une méthode updateTheMenu. Le menu ne se met pas à jour en direct, cependant, je dois cliquer dessus et le rouvrir avant que l'information mise à jour apparaisse. – Aaron

+1

Si vous utilisez 'NSRunLoopCommonModes' à la place de' NSEventTrackingRunLoopMode', cela fonctionne-t-il alors? –