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

时间:2017-06-13 17:18:41

标签: macos cocoa menubar nsmenu nsmenuitem

我正在使用菜单栏应用,并且我使用<div> <span></span> </div> NSMenuItem属性设置了自定义视图。

视图显示正常,但我无法接收任何类型的鼠标点击事件,因为这些事件具有打开的子菜单。

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

Screenshot

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

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

......但无济于事

即使没有添加按钮的方法,我也无法复制标准NSEvents的默认行为,因为NSMenuItem的{​​{1}}回调并非如此。如果它有自定义视图,则会被调用。 (我无法接收任何点击事件来自行调用)

理论上这应该是可能的,因为我能够使用默认的target-action(无自定义视图)选择​​具有开放子菜单的菜单。

有人能帮忙吗?

由于

1 个答案:

答案 0 :(得分:5)

我设置了一个像你的测试项目,NSButton s作为菜单项的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

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

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

哦,对。

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

Picard Facepalm

无论如何,我遗憾地认为没有办法让这个工作没有使用令人讨厌的黑客来使用现在的私人API如this guy did并冒着未来的破坏风险(更不用说被破坏了)来自App Store)。或者做一些事情真的疯狂就像在回溯中对其中一个C函数进行monkeypat,这可能会更糟。不过,整个问题确实值得雷达报告。请file one with Apple让他们知道这个问题,也许他们会在以后的版本中修复它。

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