【问题标题】:NSMenuItem's setEnabled doesn't work even with autoenablesItems set to NO for NSMenu即使将 NSMenu 的 autoenablesItems 设置为 NO,NSMenuItem 的 setEnabled 也不起作用
【发布时间】:2015-11-30 23:00:28
【问题描述】:

我正在维护一些旧代码,当它被提出以使用 10.8 SDK 而不是 10.7 SDK 进行编译时,某些菜单项的一些关键等效快捷方式停止工作。我的假设是这是因为这些菜单项处于禁用状态。不再工作的 NSMenuItems 是 autoenablesItems 已设置为 NO 的子菜单的一部分(我通过该子菜单的属性检查器在 xib 中确认了这一点,并通过查询 [NSMenu autoenablesItems] 以编程方式确认了这一点。它基本上出现对 [NSMenuItem setEnabled:] 的调用没有任何效果,因为如果我在调用 setEnabled:YES 后立即查询 [NSMenuItem isEnabled],则状态没有改变,它仍然为 isEnabled 返回 NO。这是一个 sn-p 代码它生成的输出:

printf("DEBUG: Current state of menu item is ");
[nsMenuItem isEnabled] ? printf("enabled\n") : printf("DISABLED!\n");
printf("DEBUG: Current state of menu autoenablesItems is ");
[nsMenu autoenablesItems] ? printf("YES\n") : printf("NO\n");

[nsMenuItem setEnabled:YES];

printf("DEBUG: Current state of menu after setting it is ");
[nsMenuItem isEnabled] ? printf("enabled\n") : printf("DISABLED!\n");

输出:

DEBUG: Current state of menu item is DISABLED!
DEBUG: Current state of menu autoenablesItems is NO
DEBUG: Current state of menu after setting it is DISABLED!

我还尝试继承 NSMenuItem 并覆盖 setEnabled 以查看是否有另一个调用 setEnabled 覆盖了我的调用,但没有其他调用通过 setEnabled。

如果我单击子菜单的父菜单,那么它似乎会更正状态并且这些 NSMenuItem 会启用,但它不会通过我的 setEnabled 代码来更改此状态。我尝试在启用时向子类添加一个观察者,以尝试在单击父菜单时捕获状态设置为启用的位置,但这也没有提供任何洞察力,因为观察者仅在我调用 setEnabled 时触发实际上并没有改变状态。

从阅读 Apple 文档中可以看出,只要父菜单上的 autoenablesItems 设置为 NO,NSMenuItem 的 setEnabled 应该可以工作,但在这种情况下似乎不起作用,我想不出出为什么。

如果我使用 10.7 SDK 编译,在相同的代码中(注意:这实际上是代码库的旧版本,所以也有其他差异,但这个特定的代码是相同的),我确实看到了调用 setEnabled 会更改 NSMenuItem 的状态。当它在 10.7 版本中输入此代码时,NSMenuItem 已经处于启用状态,但我确实尝试将调用更改为 setEnabled:NO 以确认这是否确实切换了 isEnabled 状态,并且确实如此,这与 10.8 版本不同.

任何想法为什么这在 10.8 中不起作用?我也尝试了 10.9,但它也没有在那里工作。我一直无法尝试 10.10 或 10.11,因为还有一些其他代码需要更新才能使用较新的 SDK 进行编译(同样,这是相当旧的遗留代码)。

【问题讨论】:

  • 当这些日志被命中时,你确定 nsMenuItem 实际上不是 nil 吗?这将导致两个日志都打印 NO。另一种攻击途径是在任何相关的对象(目标、第一响应者)上定义菜单验证方法,并查看 (1) 它是否被调用,以及 (2) 它的返回值是否受到尊重,即使打开自动启用也是如此离开。发生了一些可疑的事情,因为这些东西是旧代码,并且在 Cocoa 中运行良好多年。侦探工作是需要的。
  • 我已经确认 nsMenu 和 nsMenuItem 不是 nil。我还确认在单击顶级菜单项(nsMenu 是其子菜单)后,下一次调用代码时,nsMenu 和 nsMenuItem 仍然是相同的对象(调试器中的地址相同) ,然后 isEnabled 返回 true。 nsMenuItem 上的目标为 nil,但定义了一个操作。它在工作时也具有相同的状态。
  • 当等效键起作用时,我们最终得到定义动作的代码(通过 NSApplication 的 sendEvent 后跟 NSObject 的 performSelector:withObject,然后是动作)。当等效键不起作用时,从 NSApplication 的 sendEvent 我们转到 NSWindow 的 sendEvent(首先通过子类的 sendEvent),然后是 NSWindow 的 _reallySendEvent:isDelayedEvent:,然后是视图的 keyDown:。所以这个动作永远不会被调用,我认为这是因为菜单项被禁用了。
  • 用户界面中的菜单项实际上是否被禁用?显示为灰色文本等?我不禁认为 nsMenu 或 nsMenuItem 变量没有指向您认为的对象,因此您实际上是在操作错误的对象。
  • 在 UI 中打开菜单会导致状态发生变化 - 单击菜单后,快捷方式起作用并且菜单项显示为已启用。这就像试图判断冰箱里的灯是否亮着一样——通过打开门,你正在改变你试图观察的状态。除了直观地查看菜单项是否被禁用之外,还有其他方法吗?我的猜测是它们被禁用了,因为键等效项不起作用,我的理解是,如果菜单项被禁用,键等效项将被禁用。我将尝试以编程方式更改标题以确认它是同一个项目。

标签: objective-c macos cocoa nsmenu nsmenuitem


【解决方案1】:

不确定为什么 SDK 版本很重要,但您是否检查过 InterfaceBuilder 中的菜单以确保它没有为“启用”状态设置绑定?这将是物品神秘禁用的另一种方式。

否则,菜单委托可能会从您的其他代码中替换菜单项,或显式禁用项。还有一个 NSMenuDelegate 方法

- menuHasKeyEquivalent:forEvent:target:action:

在按下加速键的情况下专门调用它,并且可以完全绕过菜单项的启用/禁用状态。

【讨论】:

  • 要检查绑定,您是否查看绑定检查器?如果是这样,我在任何部分都没有看到“绑定到”。代码库中的任何地方也没有 menuHasKeyEquivalent 的实例,所以我认为这不是问题。
  • 很奇怪。我可以验证使用 10.10 和 10.11 SDK 以及一个简单的测试程序,事情确实像你期望的那样工作 - 如果菜单的 autoEnablesItems 关闭,setEnabled 和 isEnabled 工作并返回正确的结果。
  • 要回答另一个问题 - 是的,在选择菜单项后查看绑定检查器。
【解决方案2】:

简单地使用它来启用/禁用菜单项

NSMenu *menu = [[NSMenu alloc] init];

添加禁用菜单项

[menu addItemWithTitle:@"DisableItem" action:nil keyEquivalent:@""];

添加启用菜单项

[menu addItemWithTitle:@"EnableItem" action:@selector(method:) keyEquivalent:@""];

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-02-27
    • 1970-01-01
    • 2023-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-29
    相关资源
    最近更新 更多