我有一个弹出打开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];
}
这个方法肯定被调用,因为如果我单击菜单并立即返回到它,我会得到一个包含此信息的更新菜单。问题是它没有更新 - 当菜单打开时 - 。
答案 0 :(得分: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];
}
答案 1 :(得分: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指定如果存在省略号,则表示系统将在执行操作之前提示用户进行确认。但是,如果按下选项键(菜单仍然打开),您会注意到它们会变为“重新启动”和“关机”。省略号消失,这意味着如果您在按下选项键时选择它们,它们将立即执行而不会提示用户进行确认。
相同的一般功能适用于状态项中的菜单。您可以将扩展信息作为常规信息的“备用”项目,仅显示按下选项键。一旦你理解了基本原理,它实际上很容易实现,没有太多的诡计。
答案 2 :(得分: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写入。
你真正需要明确安排updateTheMenu的唯一情况就像Rob Keniger上面描述的那样,如果你试图从运行循环之外调用它。例如,您可以生成一个触发子进程并调用waitpid的线程(在进程完成之前阻塞),然后该线程必须调用performSelector:target:argument:order:modes:而不是直接调用updateTheMenu。