36

我有一个弹出打开 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];
}

肯定会调用此方法,因为如果我从菜单中单击并立即返回到它,我会得到一个包含此信息的更新菜单。问题是它没有更新-菜单打开时-。

4

3 回答 3

18

菜单鼠标跟踪是在特殊的运行循环模式 ( 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];
}
于 2010-05-11T05:08:19.747 回答
14

(如果您想更改菜单的布局,类似于机场菜单在您单击它时显示更多信息的方式,然后继续阅读。如果您想做一些完全不同的事情,那么这个答案可能不像您那样相关想要。)

关键是-[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 指定如果有省略号,则表示系统会在执行操作之前提示用户确认。但是,如果您按下选项键(菜单仍处于打开状态),您会注意到它们变为“重新启动”和“关机”。省略号消失,这意味着如果您在按下选项键的同时选择它们,它们将立即执行而不提示用户确认。

相同的一般功能适用于状态项中的菜单。您可以将扩展信息作为常规信息的“替代”项目,仅在按下选项键时显示。一旦你了解了基本原理,它实际上很容易实现,而不需要很多技巧。

于 2010-05-11T04:55:37.847 回答
13

这里的问题是,即使在菜单跟踪模式下,您也需要触发回调。

例如,-[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 写入它。

The only case you're really going to need to explicitly schedule updateTheMenu the way Rob Keniger described above is if you're trying to call it from outside of the run loop. For example, you could spawn a thread that fires off a child process and calls waitpid (which blocks until the process is done), then that thread would have to call performSelector:target:argument:order:modes: instead of calling updateTheMenu directly.

于 2012-04-25T23:56:40.600 回答