34 votes

Comment Apple met-il à jour le menu Airport alors qu'il est ouvert? (Comment changer NSMenu quand il est déjà ouvert)

J'ai un élément de la barre d'état qui s'ouvre d'un NSMenu, et j'ai un délégué ensemble et qu'il est raccordé correctement (-(void)menuNeedsUpdate:(NSMenu *)menu fonctionne très bien). Cela dit, cette méthode est configuré pour être appelée avant que le menu est affiché, j'ai besoin de l'écouter pour que et de déclencher une requête asynchrone, plus tard, la mise à jour du menu lorsqu'il est ouvert, et je ne peux pas comprendre comment c'est censé être fait.

Merci :)

MODIFIER

Ok, je suis maintenant ici:

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

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

et disposent de:

- (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 sur elle, j'obtiens un menu mis à jour avec ces informations. Le problème est qu'il n'est pas à jour alors que le menu est ouvert.

17voto

Rob Keniger Points 32985

Menu de suivi de la souris est faite dans un spécial exécuter en mode boucle (NSEventTrackingRunLoopMode). Afin de modifier le menu, vous avez besoin d'envoyer un message de sorte qu'elle sera traitée dans le suivi des événements de mode. La façon la plus simple pour ce 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 NSRunLoopCommonModes et le message sera envoyé lors de l'une des communes exécuter en boucle les modes, y compris NSEventTrackingRunLoopMode.

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

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

14voto

Dave DeLong Points 156978

(Si vous souhaitez modifier la mise en page du menu, de la même façon de l'Aéroport de menu montre plus d'informations lorsque vous cliquez sur l'option, alors continuez à lire. Si vous voulez faire quelque chose de totalement différent, alors cette réponse ne peut pas être aussi pertinentes que vous le souhaitez.)

La clé est - -[NSMenuItem setAlternate:]. Pour un exemple, disons que nous allons construire un NSMenu qui a Do something... action. Vous auriez du code que jusqu'à que 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 ce serait de créer un menu avec deux éléments: "Faire quelque chose..." et "Faire quelque chose", et vous avez en partie raison. Parce que nous avons mis le deuxième élément de menu à un autre, et parce que les deux éléments de menu ont la même clé équivalent (mais différentes modificateur de masques), alors seule la première (c'est à dire, celui qui est par défaut setAlternate:NO) s'affichera. Puis, quand vous avez le menu ouvert, si vous appuyez sur la touche de masque qui représente le deuxième (c'est à dire, la touche option enfoncée), puis l'élément de menu transformer en temps réel à partir du premier élément de menu à la seconde.

C'est, par exemple, est de savoir comment le menu Apple travaille. Si vous cliquez une fois sur elle, vous verrez quelques options avec des points de suspension après eux, tels que "Redémarrer..." et "Shutdown...". Le HIG spécifie que si il y a des points de suspension, cela signifie que le système invite l'utilisateur à des fins de confirmation avant l'exécution de l'action. Toutefois, si vous appuyez sur la touche option (avec le menu toujours ouvert), vous remarquerez qu'ils changent de "Redémarrer" et "Arrêt". Les ellipses de s'en aller, ce qui signifie que si vous les sélectionnez alors l'option touche est pressée vers le bas, elles s'exécutent immédiatement sans demander confirmation à l'utilisateur pour confirmation.

La même fonctionnalité est vrai pour les menus dans les éléments d'état. Vous pouvez avoir les informations développées être "autre" des éléments à la régulière info qui montre qu'avec la touche option enfoncée. Une fois que vous comprenez le principe de base, c'est en fait assez facile à mettre en œuvre sans tout un tas de truc.

13voto

abarnert Points 94246

Le problème ici est que vous avez besoin de votre rappel pour obtenir déclenché même dans le menu mode de suivi.

Par exemple, -[NSTask waitUntilExit] "les sondages de la boucle à l'aide de NSDefaultRunLoopMode jusqu'à la fin de la tâche". Cela signifie qu'il ne sera pas courir jusqu'à ce que après le menu se ferme. À ce stade, la planification updateTheMenu à exécuter sur NSCommonRunLoopMode n'aide pas-il ne peut pas aller en arrière dans le temps, après tout. Je crois que NSNotificationCenter, les observateurs ont également déclencher dans NSDefaultRunLoopMode.

Si vous pouvez trouver un moyen de programmer un rappel qui fonctionne même dans le menu mode de suivi, vous êtes prêt; vous pouvez les appeler updateTheMenu directement à partir de ce rappel.

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

Exécuter ce et maintenez le menu Fichier, et vous verrez l'élément de menu supplémentaire apparaît et disparaît toutes les demi-secondes. De toute évidence, "toutes les demi-secondes" n'est-ce pas ce que vous cherchez, et NSTimer ne pas comprendre "quand ma tâche en arrière-plan est fini". Mais il peut être aussi simple mécanisme que vous pouvez utiliser.

Si non, vous pouvez la créer vous-même de l'un des NSPort sous-classes-par exemple, créer un NSMessagePort et votre NSTask écrire que quand c'est bien fait.

Le seul cas où vous allez vraiment avoir besoin explicitement à l'annexe updateTheMenu la façon dont Rob Keniger décrit ci-dessus est que si vous essayez de l'appeler à partir de l'extérieur de l'exécution de la boucle. Par exemple, vous pouvez lancer un thread qui déclenche un processus enfant et les appels waitpid (qui bloque jusqu'à ce que le processus est terminé), alors que le thread aurait pu faire appel performSelector:cible:argument:ordre:modes: au lieu d'appeler updateTheMenu directement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X