强制更新 Cocoa App 主菜单的 NSMenu(嵌套子菜单)

Posted

技术标签:

【中文标题】强制更新 Cocoa App 主菜单的 NSMenu(嵌套子菜单)【英文标题】:Force NSMenu (nested submenu) update for Main Menu of Cocoa App 【发布时间】:2011-01-15 00:07:15 【问题描述】:
    我有一些子菜单作为主菜单的窗口项子菜单插入 我有一个我的对象的实例(假设它的类名是 MenuController)从 NSObject 继承并支持 NSMenuDelegate 方法中的 2: – numberOfItemsInMenu: – menu:updateItem:atIndex:shouldCancel: 此实例作为蓝色对象添加到 NIB 中,以便在运行时唤醒 步骤 2-3 中的对象配置为子菜单的委托(步骤 1)

现在,我可以在运行时提供子菜单内容了。

接下来,我将执行以下操作:我可以添加新项目或从通过协议和委托映射到实际子菜单的数组(在包含菜单标题的 MenuController 内)中删除旧项目。 一切正常。 除了一件事:我喜欢为我的动态菜单项分配快捷方式。 CMD-1、CMD-2、CMD-3等

Window / MySubmenu / MyItem1 CMD-1, MyItem2 CMD-2, ...

所以,为了调用一些我不想去 Window / MySubmenu / MyItem 用鼠标点击它的项目,我只想按一个快捷键,比如 CMD-3 来调用项目。

好的,它会定期按预期工作。但是,一般来说,除了打开 Window / MySubmenu 以重新加载其内容外,我无法通知 Main Menu 我的嵌套子菜单更改。 重现问题的一种稳定方法 - 只需尝试删除某些项目并按下分配给它的旧快捷方式,在您创建新项目作为已删除的替换后 - 宾果游戏 - 在导航到 Window / MySubmenu 以查看当前子菜单内容之前快捷方式将不起作用.

我不知道强制主菜单重建其子菜单的方法... 我试过:[[NSApp mainMenu] update] 和游戏用 NSNotificationCenter 发送 NSMenuDidAddItemNotification, NSMenuDidRemoveItemNotification, NSMenuDidChangeItemNotification

我尝试了子菜单的出口并明确调用更新方法 - 没有办法...... 有时 AppKit 调用我的委托方法——我看到了,有时它不想调用任何东西。看起来像一个随机策略。

如何确保在“一些调用”之后我的子菜单在内部数组修改后处于实际状态?

【问题讨论】:

【参考方案1】:

要实现 1:1 映射,请在委托中实现这 3 个方法:

- (BOOL)menu:(NSMenu *)menu
updateItem:(NSMenuItem *)item 
atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel

- (NSInteger)numberOfItemsInMenu:(NSMenu *)menu

- (void)menuNeedsUpdate:(NSMenu *)menu

    if (!attachedMenu)
        attachedMenu = menu;
    if (!menu)
        menu = attachedMenu;
    NSInteger count = [self numberOfItemsInMenu:menu];
    while ([menu numberOfItems] < count)
        [menu insertItem:[[NSMenuItem new] autorelease] atIndex:0];
    while ([menu numberOfItems] > count)
        [menu removeItemAtIndex:0];
    for (NSInteger index = 0; index < count; index++)
        [self menu:menu updateItem:[menu itemAtIndex:index] atIndex:index shouldCancel:NO];

attachedMenu - 是 NSMenu* 类型的内部变量

接下来,当您想要强制刷新子菜单时,随时 - 只需调用

[self menuNeedsUpdate:nil];

【讨论】:

防止内存消耗的死循环(如果在第一次没有使用 nil 调用初始化菜单) - 在 menuNeedsUpdate 中需要一个额外的条件:if (!menu) return; /* 在计数之前 = ... */【参考方案2】:

我试过了:[[NSApp mainMenu] update] ...

你在正确的轨道上。这可能是 Cocoa 应用程序中需要使用并行数组的一种情况。

    保留一个可变的菜单项数组,与菜单项表示的模型对象数组平行。 当您收到numberOfItemsInMenu: 时,将您拥有的模型对象的数量与菜单项数组的数量进行比较。如果它更少,请使用the removeObjectsInRange: method 来缩短菜单项数组。如果更多,请使用 NSNull 对象填充数组。 (这里不能使用nil,因为NSArray 只能包含对象,而nil 是没有对象。) 当您收到带有新菜单项的 menu:updateItem:atIndex:shouldCancel:、replace the object in the array at that index 时,在返回新菜单项之前。 符合the documentation for the update method 中提到的NSMenuValidation 协议。在您的验证方法find the index of the menu item within the array 中,然后在模型对象数组中的该索引处获取模型对象并从中更新菜单项。如果您使用的是 Snow Leopard,则可以向菜单项的菜单发送 propertiesToUpdate message 以确定您需要从模型对象中赋予哪些属性值。

需要注意的是,这个对象,菜单的委托,也必须是菜单项的目标。我假设它是。如果不是,这将在第 4 步失败,因为验证消息被发送到菜单项的目标。

您可能想file an enhancement request 寻求更好的方法。

【讨论】:

谢谢彼得。但是,我寻找将我的数组 1:1 映射到菜单(而不是反向)的方法。我找到了解决方案——就像你说的那样。【参考方案3】:

numberOfItemsInMenu: 的代理调用中调用removeAllItems... 这很奇怪,但是菜单不会更新,即使它正在调用代理

- (NSInteger)numberOfItemsInMenu:(NSMenu*)menu 

    [menu removeAllItems];

    return [self.templateURLs count] + 2;


【讨论】:

以上是关于强制更新 Cocoa App 主菜单的 NSMenu(嵌套子菜单)的主要内容,如果未能解决你的问题,请参考以下文章

apk如何去除强制更新

当 Cocoa 应用程序中的主线程被阻塞时,UI 不会更新

Cocoa 应用程序菜单 - 如何重新创建?

Cocoa 在多个窗口中验证菜单项

Cocoa App 在从初始 VC 转换后不会退出

仅用于文件夹的 Cocoa finder 菜单项