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

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

Спасибо:)

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

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

Ответы [ 3 ]

16 голосов
/ 11 мая 2010

Отслеживание мыши в меню осуществляется в специальном режиме цикла выполнения (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];
}
14 голосов
/ 11 мая 2010

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

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

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

13 голосов
/ 26 апреля 2012

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

Например, - [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, как описано выше Робом Кенигером, - это если вы пытаетесь вызвать его из-за пределов цикла выполнения. Например, вы могли бы порождать поток, который запускает дочерний процесс и вызывает waitpid (который блокирует, пока процесс не завершится), тогда этот поток должен был бы вызвать executeSelector: target: arguments: order: mode: вместо прямого вызова updateTheMenu.

...