Como a Apple atualiza o menu Airport enquanto está aberto? (Como mudar o NSMenu quando já estiver aberto)

Eu tenho um item de barra de status que abre um NSMenu, e eu tenho um conjunto de delegates e está ligado corretamente ( -(void)menuNeedsUpdate:(NSMenu *)menu ( -(void)menuNeedsUpdate:(NSMenu *)menu funciona bem). Dito isso, esse método é configurado para ser chamado antes que o menu seja exibido, preciso escutar isso e acionar uma solicitação assíncrona, atualizando mais tarde o menu enquanto ele estiver aberto, e não consigo descobrir como isso deve ser feito .

Obrigado 🙂

EDITAR

Ok, agora estou aqui:

Quando você clica no item de menu (na barra de status), é chamado um seletor que executa uma NSTask. Eu uso o centro de notificação para ouvir quando essa tarefa é concluída e escrever:

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

e tem:

 - (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 é definitivamente chamado porque se eu clicar fora do menu e imediatamente de volta para ele, recebo um menu atualizado com essas informações nele. O problema é que não está atualizando – enquanto o menu está aberto -.

O problema aqui é que você precisa do retorno de chamada para ser acionado mesmo no modo de rastreamento de menu.

Por exemplo, – [NSTask waitUntilExit] “pesquisa o loop de execução atual usando NSDefaultRunLoopMode até que a tarefa seja concluída”. Isso significa que não será executado até que o menu seja fechado. Nesse ponto, agendar updateTheMenu para executar em NSCommonRunLoopMode não ajuda – não pode voltar no tempo, afinal. Acredito que os observadores do NSNotificationCenter também só acionam no NSDefaultRunLoopMode.

Se você puder encontrar uma maneira de programar um retorno de chamada que seja executado mesmo no modo de rastreamento do menu, você está definido; você pode apenas chamar updateTheMenu diretamente desse retorno de chamada.

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

Executar isso e mantenha pressionado o menu Arquivo, e você verá o item de menu extra aparece e desaparece a cada meio segundo. Obviamente, “a cada meio segundo” não é o que você está procurando, e o NSTimer não entende “quando minha tarefa em segundo plano terminar”. Mas pode haver algum mecanismo igualmente simples que você possa usar.

Se não, você pode construí-lo a partir de uma das subclasss do NSPort – por exemplo, criar um NSMessagePort e fazer com que seu NSTask escreva para ele quando estiver pronto.

O único caso em que você realmente precisará agendar explicitamente updateTheMenu, como Rob Keniger descreveu acima, é se você está tentando chamá-lo de fora do loop de execução. Por exemplo, você poderia gerar um thread que triggers um processo filho e chama waitpid (que bloqueia até que o processo seja concluído), então esse thread teria que chamar performSelector: target: argument: order: modes: em vez de chamar updateTheMenu diretamente.

O rastreamento do mouse de menu é feito em um modo especial de loop de execução ( NSEventTrackingRunLoopMode ). Para modificar o menu, você precisa enviar uma mensagem para que ela seja processada no modo de rastreamento de events. A maneira mais fácil de fazer isso é usar esse método do NSRunLoop :

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

Você também pode especificar o modo como NSRunLoopCommonModes e a mensagem será enviada durante qualquer um dos modos de loop de execução comuns, incluindo NSEventTrackingRunLoopMode .

Seu método de atualização faria algo assim:

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

(Se você quiser alterar o layout do menu, semelhante a como o menu Airport mostra mais informações quando você clica nele, então continue lendo. Se você quiser fazer algo totalmente diferente, essa resposta pode não ser tão relevante quanto você gostaria.)

A chave é -[NSMenuItem setAlternate:] . Por exemplo, digamos que vamos construir um NSMenu que tenha uma ação Do something... nele. Você codifica isso como algo 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 

Agora, você pensaria que isso criaria um menu com dois itens: “Faça algo …” e “Faça algo”, e você estaria parcialmente certo. Como configuramos o segundo item de menu como uma alternativa e como os dois itens de menu têm o mesmo equivalente de chave (mas diferentes máscaras modificadoras), somente o primeiro (isto é, aquele que é por padrão setAlternate:NO ) será exibido. Então, quando você tiver o menu aberto, se você pressionar a máscara modificadora que representa a segunda (ou seja, a tecla de opção), o item de menu será transformado em tempo real do primeiro item de menu para o segundo.

Este, por exemplo, é como o menu Apple funciona. Se você clicar uma vez nela, verá algumas opções com reticências depois delas, como “Reiniciar …” e “Desligar …”. O HIG especifica que, se houver reticências, significa que o sistema solicitará ao usuário uma confirmação antes de executar a ação. No entanto, se você pressionar a tecla de opção (com o menu ainda aberto), você perceberá que elas mudam para “Reiniciar” e “Desligar”. As elipses desaparecem, o que significa que, se você selecioná-las enquanto a tecla de opção é pressionada, elas serão executadas imediatamente, sem solicitar confirmação ao usuário.

A mesma funcionalidade geral vale para os menus nos itens de status. Você pode ter as informações expandidas como itens “alternativos” para as informações regulares que só aparecem com a tecla de opção pressionada. Uma vez que você entenda o princípio básico, é bem fácil de implementar sem muitos truques.

Intereting Posts