Подтвердить что ты не робот

Как Apple обновляет меню аэропорта, пока оно открыто? (Как изменить NSMenu, когда он уже открыт)

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

Спасибо:)

ИЗМЕНИТЬ

Хорошо, я сейчас здесь:

Когда вы нажимаете на элемент меню (в строке состояния), вызывается селектор, который запускает 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];
}

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

Ответ 2

Мышь отслеживания мыши выполняется в специальном режиме цикла запуска (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];
}

Ответ 3

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

Ключ -[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 указывает, что если есть многоточие, это означает, что система предложит пользователю подтверждение перед выполнением действия. Однако, если вы нажмете клавишу выбора (при открытом меню), вы заметите, что они меняются на "Перезагрузка" и "Выключение". Эллипсы уходят, а это означает, что если вы выберете их при нажатии клавиши выбора, они будут выполняться немедленно, не запрашивая подтверждение пользователя.

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