2010-05-11 15 views
33

Tengo un elemento de la barra de estado que abre un NSMenu, y tengo un juego de delegados y está conectado correctamente (-(void)menuNeedsUpdate:(NSMenu *)menu funciona bien). Dicho esto, ese método se configura para que se llame antes de que se muestre el menú, necesito escuchar eso y activar una solicitud asincrónica, luego actualizar el menú mientras está abierto, y no puedo entender cómo se supone que debe hacerse .¿Cómo actualiza Apple el menú del aeropuerto mientras está abierto? (Cómo cambiar NSMenu cuando ya está abierto)

Gracias :)

EDIT

Ok, ahora estoy aquí:

Al hacer clic en el elemento de menú (en la barra de estado), un selector se llama que ejecuta un NSTask. Puedo usar el centro de notificación para escuchar cuando se termine esa tarea, y escribir:

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

y tienen:

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

Este método es, sin duda llama porque si hago clic sobre el menú e inmediatamente volver En él, obtengo un menú actualizado con esta información. El problema es que no está actualizando, mientras que el menú está abierto.

Respuesta

13

El problema aquí es que necesita que su devolución de llamada para ser disparado incluso en el modo de seguimiento de menús.

Por ejemplo, - [NSTask waitUntilExit] "sondea el bucle de ejecución actual utilizando NSDefaultRunLoopMode hasta que la tarea finalice". Esto significa que no se ejecutará hasta después de que se cierre el menú. En ese momento, la programación de updateTheMenu para ejecutar en NSCommonRunLoopMode no ayuda; después de todo, no puede retroceder en el tiempo. Creo que los observadores NSNotificationCenter también solo se activan en NSDefaultRunLoopMode.

Si puede encontrar la forma de programar una devolución de llamada que se ejecuta incluso en el modo de seguimiento de menú, está configurado; simplemente puede llamar a updateTheMenu directamente desde esa devolución de llamada.

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

Ejecuta esto y mantén presionado el menú Archivo, y verás que el elemento del menú adicional aparece y desaparece cada medio segundo. Obviamente, "cada medio segundo" no es lo que estás buscando, y NSTimer no entiende "cuando mi tarea de fondo haya terminado". Pero puede haber algún mecanismo igualmente simple que puedas usar.

De lo contrario, puede compilarlo usted mismo a partir de una de las subclases NSPort, por ejemplo, crear un NSMessagePort y escribir su NSTask cuando esté listo.

El único caso realmente va a necesitar programar explícitamente updateTheMenu de la manera descrita anteriormente por Rob Keniger si está intentando llamar desde fuera del bucle de ejecución. Por ejemplo, podría engendrar un hilo que desencadena un proceso hijo y llama a waitpid (que bloquea hasta que finalice el proceso), entonces ese hilo debería llamar a performSelector: target: argumento: order: modes: en lugar de llamar a updateTheMenu directamente.

+1

A pesar del largo tiempo de la pregunta, todavía tengo curiosidad por esto y estoy muy contento de ver por qué lo que tenía no funcionaba. ¡Gracias! – Aaron

13

(Si desea cambiar el diseño del menú, similar a cómo el menú del aeropuerto muestra más información cuando tiene la opción de hacer clic en él, entonces continúe leyendo. Si desea hacer algo completamente diferente, entonces esta respuesta puede no ser tan relevante como desee.)

La clave es -[NSMenuItem setAlternate:]. Por ejemplo, supongamos que construiremos un NSMenu que tenga una acción Do something.... Se podría código que como algo así como:

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 

Ahora, se podría pensar que habría que crear un menú con dos elementos en ella: "Hacer algo ..." y "hacer algo", y usted Estaría parcialmente en lo correcto. Debido a que configuramos el segundo elemento de menú para que sea alternativo, y debido a que ambos elementos de menú tienen el mismo equivalente de clave (pero máscaras de modificador diferentes), solo se mostrará el primero (es decir, el que está predeterminado setAlternate:NO). Luego, cuando tiene el menú abierto, si presiona la máscara modificadora que representa la segunda (es decir, la tecla de opción), el elemento del menú se transformará en tiempo real desde el primer elemento del menú hasta el segundo.

Esto, por ejemplo, es cómo funciona el menú de Apple. Si hace clic una vez en él, verá algunas opciones con elipsis después de ellos, como "Reiniciar ..." y "Cerrar ...". El HIG especifica que si hay una elipsis, significa que el sistema solicitará confirmación al usuario antes de ejecutar la acción. Sin embargo, si presiona la tecla de opción (con el menú todavía abierto), notará que cambian a "Reiniciar" y "Apagar". Las elipses desaparecen, lo que significa que si las selecciona con la tecla de opción presionada, se ejecutarán inmediatamente sin pedir confirmación al usuario.

La misma funcionalidad general es válida para los menús en elementos de estado. Puede hacer que la información expandida sea elementos "alternativos" a la información normal que solo aparece con la tecla de opción presionada. Una vez que comprenda el principio básico, en realidad es bastante fácil de implementar sin muchos trucos.

+4

Información extremadamente útil, aunque no era lo que estaba buscando. Definitivamente voy a encontrar un uso para eso. Estoy realmente detrás de la forma en que el menú inyecta las redes que encuentra mientras está abierto. Gracias – Aaron

16

El seguimiento del mouse de menú se realiza en un modo de ciclo de ejecución especial (NSEventTrackingRunLoopMode).Para modificar el menú, debe enviar un mensaje para que se procese en el modo de seguimiento de eventos. La forma más sencilla de hacerlo es utilizar este método de NSRunLoop:

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

También puede especificar el modo como NSRunLoopCommonModes y el mensaje será enviado durante cualquiera de los modos de bucle de ejecución comunes, incluyendo NSEventTrackingRunLoopMode.

Su método de actualización sería luego hacer algo como esto:

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

Voy a dar una oportunidad esta noche, gracias! – Aaron

+0

Esto no parece estar funcionando. Realizo mi solicitud de datos a través de NSTask, espero una notificación, al recibir esa notificación, llenó un objeto de datos que es accesible para toda la clase, y llama a su línea NSRunLoop que llama a un método updateTheMenu. El menú no se actualiza en vivo, sin embargo, tengo que hacer clic fuera de él y luego volver a abrirlo antes de que aparezca la información actualizada. – Aaron

+1

Si usa 'NSRunLoopCommonModes' en lugar de' NSEventTrackingRunLoopMode', ¿funciona entonces? –

Cuestiones relacionadas