Como a Apple atualiza o menu do aeroporto enquanto estiver aberto? (Como mudar o nsmenu quando já está aberto)

StackOverflow https://stackoverflow.com/questions/2808016

Pergunta

Eu tenho um item de statusbar que abre um nsmenu e tenho um conjunto de delegados e ele está conectado corretamente (-(void)menuNeedsUpdate:(NSMenu *)menu funciona bem). Dito isto, esse método é configurado para ser chamado antes do menu ser exibido, preciso ouvir isso e desencadear uma solicitação assíncrona, posteriormente atualizando o menu enquanto estiver aberto, e não consigo descobrir como isso deve ser feito .

Obrigado :)

EDITAR

Ok, agora estou aqui:

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

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

Esse método é definitivamente chamado porque, se eu clicar no menu e imediatamente voltar a ele, recebo um menu atualizado com essas informações. O problema é que ele não está atualizando -enquanto o menu está aberto.

Foi útil?

Solução

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

Por exemplo, -[Nstask waituntilexit] "Pesquisa o loop de corrida atual usando o NSDefaulTrunloopMode até que a tarefa seja concluída". Isso significa que ele não será executado até que o menu feche. Nesse ponto, agendar o UpdateThemenu para executar no NSCommonRunloopmode não ajuda - não pode voltar no tempo, afinal. Acredito que os observadores do NSNotificationCenter também acionam apenas no NSDefaulTrunloopMode.

Se você encontrar uma maneira de agendar um retorno de chamada que seja executado mesmo no modo de rastreamento de menu, estará definido; Você pode simplesmente ligar para o 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];
}

Execute isso e mantenha pressionado o menu do arquivo, e você verá o item de menu extra aparece e desaparecer a cada meio segundo. Obviamente, "cada meio segundo" não é o que você está procurando, e o NSTIMER não entende "quando minha tarefa de segundo plano é concluída". Mas pode haver algum mecanismo igualmente simples que você pode usar.

Caso contrário, você pode construí -lo a partir de uma das subclasses do NSPORT - EG, criar um nsmessagePort e fazer com que o NSTask escreva para isso quando estiver pronto.

O único caso que você realmente precisará agendar explicitamente a UpdateThemenu da maneira que Rob Keniger descrito acima é se você estiver tentando chamá -lo de fora do loop de corrida. Por exemplo, você pode gerar um segmento que dispara um processo filho e chama o WaitPid (que bloqueia até que o processo seja feito), então esse thread teria que chamar o performeSelector: Target: Argument: Ordem: Modos: Em vez de chamar o UpdateTethemenu diretamente.

Outras dicas

O rastreamento do mouse de menu é feito em um modo de loop de corrida especial (NSEventTrackingRunLoopMode). Para modificar o menu, você precisa despachar uma mensagem para que ele seja processado no modo de rastreamento de eventos. A maneira mais fácil de fazer isso é usar esse método de 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 corrida 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ê deseja alterar o layout do menu, semelhante à maneira como o menu do aeroporto mostra mais informações quando você opção, clique nele, continue lendo. Se você quiser fazer algo completamente diferente, essa resposta pode não ser tão relevante quanto você 'D gosto.)

A chave é -[NSMenuItem setAlternate:]. Por exemplo, digamos que vamos construir um NSMenu isso tem um Do something... ação nele. Você codificou 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. Porque definimos o segundo item de menu como alternativo e como os dois itens de menu têm a mesma chave equivalente (mas diferentes máscaras de modificador), então apenas o primeiro (ou seja, aquele que é por padrão setAlternate:NO) mostrará. Então, quando você abrir o menu, se você pressionar a máscara do modificador que representa a segunda (ou seja, a tecla de opção), o item de menu se transformará em tempo real desde o primeiro item de menu até o segundo.

Por exemplo, é assim que o menu da Apple funciona. Se você clicar uma vez, verá algumas opções com elipses depois delas, como "reiniciar ..." e "desligamento ...". O HIG especifica que, se houver uma elipse, significa que o sistema solicitará ao usuário confirmar antes de executar a ação. No entanto, se você pressionar a tecla Opção (com o menu ainda aberto), você notará que eles mudam para "reiniciar" e "desligar". As elipses desaparecem, o que significa que, se você as selecionar enquanto a tecla de opção for pressionada, elas serão executadas imediatamente sem solicitar o usuário para confirmação.

A mesma funcionalidade geral é verdadeira para os menus em itens de status. Você pode ter as informações expandidas ser itens "alternados" para as informações regulares que aparecem apenas com a tecla Opção é pressionada. Depois de entender o princípio básico, é realmente muito fácil de implementar sem muitos truques.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top