代码之家  ›  专栏  ›  技术社区  ›  Aaron

苹果如何在机场菜单打开时更新它?(如何在NSMENU已打开时更改它)

  •  33
  • Aaron  · 技术社区  · 15 年前

    我有一个弹出的状态栏项打开一个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];
    }
    

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

    3 回复  |  直到 12 年前
        1
  •  13
  •   abarnert    13 年前

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

    例如,-[nstask waituntlexit]“使用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。

        2
  •  16
  •   Rob Keniger    15 年前

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

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

    还可以将模式指定为 NSRunLoopCommonModes 消息将在任何常见的运行循环模式中发送,包括 N事件跟踪卸载模式 .

    然后,更新方法将执行如下操作:

    - (void)updateTheMenu:(NSMenu*)menu
    {
        [menu addItemWithTitle:@"Foobar" action:NULL keyEquivalent:@""];
        [menu update];
    }
    
        3
  •  14
  •   Dave DeLong    15 年前

    (如果您想更改菜单的布局,类似于当您选择单击时机场菜单显示更多信息的方式,请继续阅读。如果你想做一些完全不同的事情,那么这个答案可能没有你想要的那么重要。)

    关键是 -[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 )将展示。然后,当您打开菜单时,如果您按下表示第二个菜单项的修改器遮罩(即选项键),则菜单项将从第一个菜单项实时转换为第二个菜单项。

    例如,这就是苹果菜单的工作原理。如果你点击一下它,你会看到一些后面有省略号的选项,比如“重启…”和“关机…”。hig指定如果存在省略号,则意味着系统将在执行操作之前提示用户确认。但是,如果按选项键(菜单仍处于打开状态),您会发现它们变为“重新启动”和“关机”。省略号消失,这意味着如果在按下选项键的同时选择它们,它们将立即执行,而不提示用户确认。

    对于状态项中的菜单,相同的常规功能同样适用。您可以将展开的信息设为“备用”项,以显示常规信息,只需按下选项键即可。一旦你了解了基本原理,它实际上很容易实现,而不需要太多的技巧。