2010-05-11 2 views
33

У меня есть элемент состояния, который открывает NSMenu, и у меня есть набор делегатов, и он подключен правильно (-(void)menuNeedsUpdate:(NSMenu *)menu отлично работает). Тем не менее, этот метод должен быть вызван до отображения меню, мне нужно его прослушать и вызвать асинхронный запрос, позже обновив меню, пока он открыт, и я не могу понять, как это должно быть сделано ,Как Apple обновляет меню аэропорта, пока оно открыто? (Как изменить NSMenu, когда он уже открыт)

Спасибо :)

EDIT

Хорошо, теперь я здесь:

При нажатии на пункт меню (в строке состояния), селектор называется который запускает NSTask. Я использую центр уведомлений для прослушивания, когда эта задача будет завершена, и написать:

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

и есть:

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

Этот метод определенно называется потому, что, если я нажму из меню и сразу же обратно на него я получаю обновленное меню с этой информацией. Проблема в том, что он не обновляется, а меню открыто.

ответ

13

Проблема здесь состоит в том, что вам нужен обратный вызов, чтобы получить срабатывает даже в режиме отслеживания меню.

Например, [NSTask waitUntilExit] "опроса текущего цикла цикла с использованием NSDefaultRunLoopMode до завершения задачи". Это означает, что он не будет запускаться до тех пор, пока меню не закроется. В этот момент планирование updateTheMenu для запуска на NSCommonRunLoopMode не помогает - в конце концов, оно не может вернуться во времени. Я считаю, что наблюдатели NSNotificationCenter также запускаются только в NSDefaultRunLoopMode.

Если вы можете найти способ запланировать обратный вызов, который запускается даже в режиме отслеживания меню, вы настроены; вы можете просто вызвать updateTheMenu непосредственно из этого обратного вызова.

Запустите это и удерживайте меню «Файл», и вы увидите, что дополнительный пункт меню появляется и исчезает каждые полсекунды. Очевидно, что «каждая половина секунды» не то, что вы ищете, и NSTimer не понимает «когда моя фоновая задача завершена». Но может быть и такой же простой механизм, который вы можете использовать.

Если нет, вы можете создать его самостоятельно из одного из подклассов NSPort, например, создать NSMessagePort и написать NSTask, когда это будет сделано.

Единственный случай, когда вам действительно понадобится явно планировать обновление. Момент, описанный выше, описанный Роба Кенигером, заключается в том, что вы пытаетесь вызвать его из-за пределов цикла выполнения. Например, вы можете создать поток, который запускает дочерний процесс и вызывает waitpid (который блокирует до тех пор, пока процесс не будет завершен), тогда этот поток должен был бы вызвать performSelector: target: argument: order: modes: вместо прямого вызова updateTheMenu.

+1

Несмотря на долгое время с вопроса, мне все еще было интересно об этом, и я очень рад видеть, почему то, что у меня было, не работает. Спасибо! – Aaron

13

(Если вы хотите изменить макет меню, аналогично тому, как меню аэропорта показывает больше информации, когда вы выбираете его, а затем продолжайте читать. Если вы хотите сделать что-то совершенно другое, тогда этот ответ может не быть насколько это было бы необходимо.)

Ключ -[NSMenuItem setAlternate:]. Например, предположим, что мы собираемся построить NSMenu, в котором есть действие Do something.... Вы бы закодировать, что до, как что-то вроде:

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 

Теперь можно подумать, что это будет создавать меню с двумя пунктами в нем: «Сделай что-нибудь ...» и «Сделай что-нибудь», и вы «Отчасти правильно. Поскольку мы устанавливаем второй элемент меню как альтернативный, и потому, что оба элемента меню имеют одинаковый эквивалент ключа (но разные маски модификатора), тогда будет отображаться только первый (то есть тот, который по умолчанию setAlternate:NO). Затем, когда вы открываете меню, если вы нажмете маску модификатора, которая представляет вторую (т. Е. Клавишу выбора), тогда пункт меню преобразуется в реальном времени из первого пункта меню во второй.

Это, например, то, как работает меню Apple. Если вы нажмете на него один раз, вы увидите несколько вариантов с эллипсами после них, например «Перезагрузка ...» и «Выключение ...». HIG указывает, что если есть многоточие, это означает, что система предложит пользователю подтверждение перед выполнением действия. Однако, если вы нажмете клавишу выбора (при открытом меню), вы заметите, что они меняются на «Перезагрузка» и «Выключение». Эллипсы уходят, а это означает, что если вы выберете их при нажатии клавиши выбора, они будут выполняться немедленно, не запрашивая подтверждение пользователя.

Такая же общая функциональность сохраняется для меню в статусных элементах. Вы можете иметь расширенную информацию как «альтернативные» элементы для регулярной информации, которая отображается только при нажатии клавиши выбора. Как только вы поймете основной принцип, на самом деле его довольно легко реализовать без целых обманов.

+4

Чрезвычайно полезная информация, хотя и не то, для чего я собирался. Однако я обязательно найду для этого какое-то использование. Я действительно после того, как меню вводит сети, которые он находит, пока он открыт. Спасибо – Aaron

16

Мышь отслеживания мыши выполняется в специальном режиме цикла (NSEventTrackingRunLoopMode).Чтобы изменить меню, вам необходимо отправить сообщение, чтобы оно было обработано в режиме отслеживания событий. Самый простой способ сделать это, чтобы использовать этот метод NSRunLoop:

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

Вы также можете задать режим, как NSRunLoopCommonModes и сообщение будет отправлено в течение любого из распространенных режимов цикла выполнения, в том числе NSEventTrackingRunLoopMode.

Ваш метод обновления затем сделать что-то вроде этого:

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

Собираюсь дать этот снимок сегодня вечером, спасибо! – Aaron

+0

Это, похоже, не работает. Я делаю запрос на получение данных через NSTask, дождавшись уведомления, после получения этого уведомления я заполняю объект данных, доступный для всего класса, и вызываю строку NSRunLoop, которая вызывает метод updateTheMenu. Однако меню не обновляется, но я должен щелкнуть его, а затем снова открыть его до появления обновленной информации. – Aaron

+1

Если вы используете 'NSRunLoopCommonModes' вместо' NSEventTrackingRunLoopMode', тогда он работает? –

 Смежные вопросы

  • Нет связанных вопросов^_^