Apple如何在打开时更新机场菜单?(如何在NSMenu已经打开时更改)

Aar*_*ron 34 cocoa objective-c statusbar nsmenu nsmenuitem

我有一个弹出打开NSMenu的状态栏项目,我有一个委托集,它正确连接(-(void)menuNeedsUpdate:(NSMenu *)menu工作正常).也就是说,该方法设置为在显示菜单之前调用,我需要监听并触发异步请求,稍后在菜单打开时更新菜单,我无法弄清楚应该如何完成.

谢谢 :)

编辑

好的,我现在在这里:

单击菜单项(在状态栏中)时,将调用一个运行NSTask的选择器.我使用通知中心来监听该任务何时完成,并写下:

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

并有:

- (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];
}
Run Code Online (Sandbox Code Playgroud)

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

Rob*_*ger 17

菜单鼠标跟踪以特殊的运行循环模式(NSEventTrackingRunLoopMode)完成.要修改菜单,您需要发送一条消息,以便在事件跟踪模式下处理它.最简单的方法是使用以下方法NSRunLoop:

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

您还可以将模式指定为,NSRunLoopCommonModes并且在任何常见的运行循环模式期间将发送消息,包括NSEventTrackingRunLoopMode.

您的更新方法将执行以下操作:

- (void)updateTheMenu:(NSMenu*)menu
{
    [menu addItemWithTitle:@"Foobar" action:NULL keyEquivalent:@""];
    [menu update];
}
Run Code Online (Sandbox Code Playgroud)


Dav*_*ong 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
Run Code Online (Sandbox Code Playgroud)

现在,你认为这将创建一个包含两个项目的菜单:"做点什么......"和"做点什么",你就部分正确了.因为我们将第二个菜单项设置为备用项,并且因为两个菜单项具有相同的等效键(但是具有不同的修改器掩码),所以仅显示第一个菜单项(即默认情况下的那个setAlternate:NO).然后,当您打开菜单时,如果按下代表第二个菜单的修改器蒙版(即选项键),则菜单项将实时从第一个菜单项转换为第二个菜单项.

例如,这就是Apple菜单的工作原理.如果单击一次,您将看到一些带有省略号的选项,例如"重新启动..."和"关闭...".HIG指定如果存在省略号,则表示系统将在执行操作之前提示用户进行确认.但是,如果按下选项键(菜单仍然打开),您会注意到它们会变为"重新启动"和"关机".省略号消失,这意味着如果在按下选项键时选择它们,它们将立即执行而不会提示用户进行确认.

状态项中的菜单也具有相同的一般功能.您可以将扩展信息作为常规信息的"备用"项目,仅显示按下选项键.一旦你理解了基本原理,它实际上很容易实现,没有大量的诡计.

  • 非常有用的信息,虽然不是我想要的.我肯定会找到它的用途.我真的是在菜单注入打开时找到的网络的方式之后.谢谢 (4认同)

aba*_*ert 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];
}
Run Code Online (Sandbox Code Playgroud)

运行此按钮并按住文件菜单,您将看到额外的菜单项出现并每半秒消失.显然"每半秒钟"不是你想要的,而且NSTimer不理解"当我的后台任务完成时".但是可以使用一些同样简单的机制.

如果没有,你可以自己从一个NSPort子类中构建它 - 例如,创建一个NSMessagePort并在完成后让你的NSTask写入.

你真正需要明确安排updateTheMenu的唯一情况就像Rob Keniger上面描述的那样,如果你试图从运行循环之外调用它.例如,您可以生成一个触发子进程并调用waitpid的线程(在进程完成之前阻塞),然后该线程必须调用performSelector:target:argument:order:modes:而不是直接调用updateTheMenu.

  • 尽管提出这个问题已经很长时间了,但我仍然对此感到好奇,我很高兴看到为什么我所拥有的不起作用。谢谢! (2认同)