【问题标题】:How does Apple update the Airport menu while it is open? (How to change NSMenu when it is already open)Apple 如何在打开时更新 Airport 菜单? (如何在已经打开的情况下更改 NSMenu)
【发布时间】:2011-02-17 23:28:09
【问题描述】:

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

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

【问题讨论】:

    标签: objective-c cocoa statusbar nsmenu nsmenuitem


    【解决方案1】:

    菜单鼠标跟踪是在特殊的运行循环模式下完成的 (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];
    }
    

    【讨论】:

    • 这似乎不起作用。我通过 NSTask 请求数据,等待通知,在收到该通知后,我填充一个可供整个类访问的数据对象,并调用调用 updateTheMenu 方法的 NSRunLoop 行。菜单不会实时更新,但我必须单击它然后重新打开它才能显示更新的信息。
    • 如果你使用NSRunLoopCommonModes而不是NSEventTrackingRunLoopMode,那么它会起作用吗?
    • 当我这样做时,根本没有调用 updateTheMenu,这让我很困惑。
    【解决方案2】:

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

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

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

    【讨论】:

    • 非常有用的信息,虽然不是我想要的。不过,我肯定会找到它的用途。我真的很喜欢菜单在打开时注入它找到的网络的方式。谢谢
    【解决方案3】:

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

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

    您真正需要以上述 Rob Keniger 的方式显式安排 updateTheMenu 的唯一情况是,如果您尝试从运行循环之外调用它。例如,您可以生成一个线程来触发子进程并调用 waitpid(它会阻塞直到进程完成),然后该线程必须调用 performSelector:target:argument:order:modes: 而不是直接调用 updateTheMenu。

    【讨论】:

    • 尽管这个问题已经很久了,但我仍然对此感到好奇,我很高兴看到为什么我没有工作。谢谢!
    猜你喜欢
    • 2012-04-25
    • 1970-01-01
    • 1970-01-01
    • 2021-08-22
    • 1970-01-01
    • 2011-02-25
    • 2022-01-15
    • 2014-01-19
    • 1970-01-01
    相关资源
    最近更新 更多