具有自定义视图的NSMenuItem不会接收鼠标事件

Ste*_*veB 9 macos cocoa menubar nsmenu nsmenuitem

我正在使用菜单栏应用程序,我正在使用NSMenuItem's view属性设置自定义视图.

视图显示正常,但我无法接收具有打开子菜单的菜单项的任何类型的鼠标单击事件.

在此屏幕截图中,我为每个项目添加了一个按钮.3个最右边的按钮功能正常,但父菜单中的按钮根本不会收到任何点击事件.

截图

我尝试了很多东西,包括:

  • 尝试使用mouseUpmouseDown方法捕获鼠标事件
  • NSWindow当鼠标进入该视图时,为自定义视图键设置
  • 添加全局和本地监视器 NSEvents

......但无济于事

即使没有添加按钮的方法,我也无法复制标准的默认行为NSMenuItem,因为如果它具有自定义视图target-action,NSMenuItem则不会调用它的回调.(我自己无法收到任何点击事件)

理论上这应该是可能的,因为我能够使用默认选项NSMenuItem(没有自定义视图)选择具有打开子菜单的菜单.

有人能帮忙吗?

谢谢

Cha*_*tka 7

我设置了一个像你的测试项目,用NSButtons作为view菜单项,看到了你看到的相同行为.这确实很吸引人.如果你继承NSApplication并覆盖其-sendEvent:方法,增加了日志,看看通过这一机制去什么事件,你会发现-sendEvent:,当你点击任何菜单项,即使是的那些是从来没有真正所谓的工作.这不是很奇怪吗?所以接下来要尝试的是子类化NSButton,添加覆盖-mouseDown:,并在那里放置一个断点.果然,对于具有打开子菜单的项目,断点永远不会被击中,但是对于其他项目来说它会被击中.当我们这样做时,回溯是:

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100002fa0 menutest`MyButton.mouseDown(event=0x0000608000121900, self=0x0000600000140a50) at AppDelegate.swift:33
    frame #1: 0x000000010000303c menutest`@objc MyButton.mouseDown(with:) at AppDelegate.swift:0
    frame #2: 0x00007fffa9f6724f AppKit`-[NSWindow(NSEventRouting) _handleMouseDownEvent:isDelayedEvent:] + 6341
    frame #3: 0x00007fffa9f63a6c AppKit`-[NSWindow(NSEventRouting) _reallySendEvent:isDelayedEvent:] + 1942
    frame #4: 0x00007fffa9f62f0a AppKit`-[NSWindow(NSEventRouting) sendEvent:] + 541
    frame #5: 0x00007fffa9a2328d AppKit`-[NSCarbonWindow sendEvent:] + 118
    frame #6: 0x00007fffa9a20261 AppKit`NSMenuItemCarbonEventHandler + 10597
    frame #7: 0x00007fffab0acd85 HIToolbox`DispatchEventToHandlers(EventTargetRec*, OpaqueEventRef*, HandlerCallRec*) + 1708
    frame #8: 0x00007fffab0abff6 HIToolbox`SendEventToEventTargetInternal(OpaqueEventRef*, OpaqueEventTargetRef*, HandlerCallRec*) + 428
    frame #9: 0x00007fffab0c1d14 HIToolbox`SendEventToEventTarget + 40
    frame #10: 0x00007fffab0ea7df HIToolbox`ToolboxEventDispatcherHandler(OpaqueEventHandlerCallRef*, OpaqueEventRef*, void*) + 2503
    frame #11: 0x00007fffab0ad17a HIToolbox`DispatchEventToHandlers(EventTargetRec*, OpaqueEventRef*, HandlerCallRec*) + 2721
    frame #12: 0x00007fffab0abff6 HIToolbox`SendEventToEventTargetInternal(OpaqueEventRef*, OpaqueEventTargetRef*, HandlerCallRec*) + 428
    frame #13: 0x00007fffab0c1d14 HIToolbox`SendEventToEventTarget + 40
    frame #14: 0x00007fffab12e928 HIToolbox`IsUserStillTracking(MenuSelectData*, unsigned char*) + 1658
    frame #15: 0x00007fffab255dc4 HIToolbox`TrackMenuCommon(MenuSelectData&, unsigned char*, SelectionData*, MenuResult*, MenuResult*) + 1664
    frame #16: 0x00007fffab13a223 HIToolbox`MenuSelectCore(MenuData*, Point, double, unsigned int, OpaqueMenuRef**, unsigned short*) + 554
    frame #17: 0x00007fffab139f66 HIToolbox`_HandleMenuSelection2 + 460
    frame #18: 0x00007fffa97ee368 AppKit`_NSHandleCarbonMenuEvent + 239
    frame #19: 0x00007fffa9a68702 AppKit`_DPSEventHandledByCarbon + 54
    frame #20: 0x00007fffa9de90c5 AppKit`-[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 963
    frame #21: 0x00007fffa96623db AppKit`-[NSApplication run] + 926
    frame #22: 0x00007fffa962ce0e AppKit`NSApplicationMain + 1237
    frame #23: 0x00000001000035fd menutest`main at AppDelegate.swift:13
    frame #24: 0x00007fffc12fc235 libdyld.dylib`start + 1
Run Code Online (Sandbox Code Playgroud)

如您所见,事件不是通过Cocoa事件调度机制调度的,因为菜单实际上是Carbon.这是正确的,那些在过渡到64位时被认为已被删除的碳API和子系统实际上仍然非常活跃.它们现在只是私有API.我们不能在64位模式下使用它们,但Apple确实可以,并且整个菜单系统仍然在Carbon事件模型之上实现.因为第三方开发人员不得不从头开始重写Photoshop,但是1997年有人写的菜单处理代码太有价值而不能放弃,我相信你同意.

无论如何,我通过调出-[NSCarbonWindow sendEvent:],这个回溯中最早的Objective-C方法(除了非常顶级的东西)进行了一些测试,看看当点击子菜单项时它是否被调用,而事实并非如此.所以,如果我不得不猜测,我会说问题出在Carbon事件处理程序中.嗯,这可能是后端的一点痛,但嘿,没问题!我们可以通过下降到Carbon级别并安装我们自己的Carbon事件处理程序来解决这个问题.好吧,卷起​​袖子,让我们做吧

啊对.

我们不能在64位模式下使用这些API.

皮卡德Facepalm

无论如何,我遗憾地认为没有办法让这个工作没有使用讨厌的黑客来使用现在的私人API,就像这个家伙做的那样,并冒着未来的破坏风险(更不用说从App Store中定义) .或者做一些非常疯狂的事情,比如在回溯中对其中一个C函数进行monkeypat,这可能会更糟.不过,整个问题确实值得雷达报告.请向Apple提交一份文件,让他们知道这个问题,也许他们会在将来的某个版本中修复它.

编辑:实际上有一种解决方案.由于附加到没有子菜单的菜单项的视图确实会收到您期望的鼠标事件,因此您可以放弃设置submenu,只需查看捕获mouseEntered:mouseExited:事件并自己显示菜单,从而模拟子菜单.这不是世界上最理想的解决方案,但它至少是一些东西.