了解NSView / NSWindow鼠标捕获和分发

时间:2017-06-01 13:02:59

标签: objective-c macos cocoa nsview nswindow

我正在尝试理解在按下鼠标按钮时Cocoa如何发送鼠标消息。

我的背景是在Window的编程中,所以这是我来自的视角......

  1. 窗口发送鼠标按下事件
  2. 该应用程序可以调用SetCapture(hWnd)来请求将所有未来的鼠标事件传递到该窗口,而不是鼠标下的窗口
  3. 应用程序在完成恢复正常鼠标调度时调用ReleaseCapture
  4. 该应用可能会收到WM_CANCELMODE消息,指示其释放捕获并取消任何跟踪模式。 (例如:如果在鼠标拖动操作期间Alt-Tab离开,Windows会发送此消息)
  5. 所以我在Cocoa上学习,我理解的方式是......

    1. NSWindow / NSView收到NSLeftMouseDown事件
    2. Cocoa自动“捕获”鼠标并发送NSLeftMouseDragged事件,同时将鼠标按钮保持在单击的窗口/视图中。
    3. NSWindow / NSView收到NSLeftMouseUp事件
    4. 这是我的问题:

      1. 是否有办法让窗口取消捕获的鼠标并明确地将其重新捕获到另一个窗口?
      2. 有没有办法找出哪个窗口(和/或视图)当前有“捕获”鼠标?
      3. 是否存在视图可能会收到不平衡的mouseDown / Up事件的情况?
      4. 是否有像WM_CANCELMODE这样的事件我应该注意取消当前的跟踪操作?
      5. 为了解释我想要实现的目标,我的应用程序中有一个滑块控件:

        Slider

        鼠标按下时会在弹出窗口中显示更大的版本:

        SliderCap

        当它弹出时,鼠标光标会自动移动到较大的滑块手柄并跟踪鼠标,直到它被释放,然后移除弹出窗口并将光标移回较小的滑块。 (Here's a video显示)

        我需要将捕获的鼠标事件重定向到弹出窗口NSWindow,或者我的鼠标跟踪循环已知哪个NSWindow当前捕获了鼠标。我可以通过跟踪鼠标事件来解决这个问题但是认为可能有一个API来获取它(例如:像Window的GetCapture()API)。

        顺便说一句:我实际上有这个工作,但我做的事情感觉有点hacky,我想更好地理解OSX的方法 - 并确保我不会错过任何明显/更容易的事情。

1 个答案:

答案 0 :(得分:3)

注意: 我开始回答这个问题,结果却离我而去了。很抱歉长篇大论,但我相信我已经涵盖了您需要知道的有关事件和响应者的所有内容,以及一些我随着时间的推移遇到的问题的提示。希望我的解释简单易懂,如果您需要更多详细说明,请告诉我^^

我打算说 - 基于你的视频看起来你有它的工作:)

所以有很多文档可以解释我将要解释的内容,但是我将尝试分享我对关键点的简化理解,希望这比Apple Reference更有用

首先,更粗略的操作系统概念,有人可能会纠正我(对你的问题不是那么重要):

  • 有“运行循环”,基本上可以监听和处理事件。每个运行循环都与一个线程
  • 相关联
  • 与主线程关联的运行循环是接收鼠标事件和键盘事件的运行循环

然后,我更积极的概念是:

  • 收到活动后,会通过消息-sendEvent:(结帐NSEvent中的NSApplication类别)
  • 将其传递到活动应用程序
  • 应用程序确定关键窗口,并确定事件的相应消息(鼠标左键单击将获得-mouseDown:事件)
  • 接收事件的窗口确定“第一响应者”。请注意,任何NSView实际上都是从NSResponder继承的,任何NSResponder都可以在响应者链中。 NSWindow和其他AppKit对象也是NSResponder的子类[1]: https://i.
  • 如果事件是鼠标事件,它实际上会将事件发送到鼠标下方的最顶层视图
  • 您可以覆盖-mouseDown:-mouseDragged:或其他任何事件(唯一参数),并且您可以将其传递给您想要的任何NSResponder,只要您当然可以参考
  • 如果对象没有响应该事件,则会将其传递给响应者链
  • 响应者链实际上就像一个单链表。头节点是[NSWindow firstResponder],每个NSResponder都有一个名为nextResponder的属性

最后,这是我们的一个对象中-mouseDown:事件的断点的屏幕截图

enter image description here

注意我们在主线程中,在运行循环中,我们的应用程序首先获取事件,将其传递给窗口,窗口确定第一个响应者(CanvasMaskView因为这是鼠标单击,这个是鼠标下的最顶层视图,我们实际上手动将事件传递给响应者链 enter image description here

然后在响应者链上进行枚举,直到我们最终找到一个处理-mouseDown:的对象,在顶部的ImageController

最后,请注意调用堆栈中的所有forwardMethods?这是在nextResponder nextResponder的{​​{1}} nextResponder等传递的事件。

这可以通过在每个堆栈帧检出$r13寄存器来证明,该堆栈帧包含事件的当前接收者。请注意,在此屏幕截图中,我单击了堆栈帧,并在lldb中使用了po $r13,然后单击下一个堆栈帧,并执行相同的操作: enter image description here

这一点很重要,因为你可能会在你不知道的链中有一个响应者,它会消耗这个事件并且不会传递它,除非你调查这个链,否则你不会知道。通过“传递”,我的意思是你需要调用[super mouseDown],如果需要,它将自动传递事件。

哦是的,最后的注意!如果你压倒-mouseDown我发现你必须打电话给super mouseDown。否则-mouseUp:事件将消失。虽然这是轶事,我很确定[super mouseDown]可能会在操作系统中注册视图,因此它知道在哪里发送-mouseUp:猜测