【问题标题】:Implementing a Custom Cocoa Event Tracking Loop实现自定义 Cocoa 事件跟踪循环
【发布时间】:2017-05-16 03:24:49
【问题描述】:

我正在开发一个自定义跨平台 UI 库,该库需要一个同步的“ShowPopup”方法来显示一个弹出窗口,运行一个事件循环直到它完成,并在点击弹出窗口外部或按 Esc 键时自动取消。键盘、鼠标和滚轮事件需要分派到弹出窗口,但其他事件(绘画、绘图、计时器等)需要在循环运行时分派到它们的常规目标。

编辑:为了澄清起见,弹出窗口是指这种菜单样式的弹出窗口,而不是警报/对话框等...

在 Windows 上,我通过调用 GetMessage/DispatchMessage 并根据需要过滤和分派消息来实现这一点。工作正常。

但是,我对 Cocoa/OS X 的经验要少得多,并且发现整个事件循环/调度范例有点令人困惑。我看过以下文章,它解释了如何实现与我需要的非常相似的鼠标跟踪循环:

http://stpeterandpaul.ca/tiger/documentation/Cocoa/Conceptual/EventOverview/HandlingMouseEvents/chapter_5_section_4.html

但是...这有一些让我担心的事情。

  1. 链接的文章指出:“应用程序的主线程在事件跟踪循环期间无法处理任何其他请求,并且计时器可能不会触发”。可能不?为什么不,如果不,如何确保他们这样做?

  2. nextEventMatchingMask:untilDate:inMode:dequeue: 的文档指出“与指定事件类型之一不匹配的事件将留在队列中。”。这似乎有点奇怪。这是否意味着如果事件循环只要求鼠标事件,那么一旦循环完成,任何按下的键都会被处理?那会很奇怪。

  3. 是否可以在不删除事件队列的情况下查看消息。例如:我的库的 Windows 版本在外部单击时使用它来关闭弹出窗口,但将单击事件留在队列中,以便在另一个按钮上单击弹出窗口外部不需要第二次单击。

  4. 我已经阅读并重新阅读了有关运行循环模式的内容,但仍然没有真正理解。很好地解释这些是什么。

  5. 还有其他实现弹出事件循环的好例子吗?更好的做法是使用内置 NSApplication 运行循环的伪代码。

另一种表述方式... Cocoa 相当于 Windows 的 PeekMessage(..., PM_REMOVE)PeekMessage(..., PM_NOREMOVE)DispatchMessage()

非常感谢任何帮助。

【问题讨论】:

  • 与用户交互或旋转事件循环的同步方法是虚构的。他们公然对用户撒谎,然后出现错误,因为从未打算重新输入的代码是。我强烈建议根本没有任何同步事件循环旋转方法。没有任何。在这个世纪和十年里,没有人应该再这样编程了。
  • 当 UI 元素改变状态 - 即弹出窗口被关闭时,会从事件循环/调度程序内向相关方发出异步通知。如果应用程序不是微不足道的,您将希望在由这些事件驱动的(例如 UML/SCXML)状态机中正式描述其行为,否则您可以直接使用它们。重新进入事件循环是不可移植的——即在不同的平台上,不同的可重入事件将从子循环发出,并且它颁布了直接导致意大利面条代码的虚构。 UI 不同步。从来没有。
  • @KubaOber 谢谢 - 上传图片。至于旋转同步事件循环——这正是 Apple 文档所描述的,所以我不认为它是“虚构的”。此外,我最终使用 Ken Thomases 回答的提示使其完美运行。

标签: macos cocoa


【解决方案1】:

您使用的术语“弹出窗口”到底是什么?该术语在不同的 GUI API 中意味着不同的东西。它只是一个模态对话框窗口吗?


问题编辑更新:

您似乎只想实现一个自定义菜单。 Apple 提供了一个示例项目CustomMenus,它说明了该技术。它是 WWDC 2010 会议视频之一的伴侣,Session 145, "Key Event Handling in Cocoa Applications"


根据您需要实现的具体目标,您可能需要使用NSAlert。或者,您可以使用自定义窗口并使用NSApplication-runModalForWindow: 方法以模态方式运行它。

为了满足您在用户在窗口外单击时结束模态会话的要求,您可以使用本地事件监视器。在(现代的、当前的)Cocoa 事件处理指南中甚至有一个这样的功能示例:Monitoring Events

说了这么多,以下是(希望不再相关)您具体问题的答案:

  1. 链接文章指出:“应用程序的主线程在事件跟踪循环期间无法处理任何其他请求,并且 计时器可能不会触发“。可能不会?为什么不,当不时,如何制作 确定吗?

因为计时器是在特定的运行循环模式或一组模式中安排的。请参阅下面问题 4 的答案。您通常会在运行事件跟踪循环时使用事件跟踪模式,因此未安排在该模式下的计时器将不会运行。

您可以为事件跟踪循环使用默认模式,但这确实不是一个好主意。这可能会导致意外的重新进入。

假设您的弹出窗口类似于模式窗口,您可能应该使用NSModalPanelRunLoopMode

  1. nextEventMatchingMask:untilDate:inMode:dequeue: 的文档 状态“与指定事件类型之一不匹配的事件是 留在队列中。”。这似乎有点奇怪。这是否意味着如果 事件循环只要求鼠标事件,然后任何按下的键都将是 循环完成后处理?那会很奇怪。

是的,就是这个意思。防止这种奇怪的结果由你决定。如果您要阅读这十年的 Cocoa 事件处理指南版本,您会发现有 a section on how to deal with this。 ;-P

  1. 是否可以在不删除事件队列的情况下查看消息。例如:我的库的 Windows 版本使用它来关闭 在外部单击时弹出窗口,但将单击事件留在 排队,这样就不会在弹出窗口之外单击另一个按钮 需要再次点击。

是的。你注意到nextEventMatchingMask:untilDate:inMode:dequeue: 的“dequeue:”参数了吗?如果您为此传递NO,则该事件将留在队列中。

  1. 我已经阅读并重新阅读了有关运行循环模式的内容,但仍然没有真正理解。很好地解释这些是什么。

如果不知道您对什么感到困惑以及Apple guide 是如何让您失望的,您很难知道该告诉您什么。

您是否熟悉使用围绕select()poll()epoll()kevent() 的循环来处理多个异步通信通道?有点像那样,但更加自动化。您不仅构建了一个数据结构,其中列出了您想要监视的输入源以及您感兴趣的输入源上的特定事件,而且每个输入源还具有与之关联的回调。运行 run loop 就像调用上述函数之一来等待输入,而且当输入到达时,调用与源关联的回调来处理该输入。您可以运行该循环的单圈,运行到特定时间,甚至无限期运行。

使用运行循环,可以将输入源组织成集合。这些集合称为“模式”并由名称(即字符串)标识。当你运行一个运行循环时,你可以通过指定它应该在哪种模式下运行来指定它应该监控哪一组输入源。其他输入源仍然是运行循环知道的,但只是暂时忽略了。

-nextEventMatchingMask:untilDate:inMode:dequeue: 方法或多或少是在内部运行线程的运行循环。除了运行循环中已经存在的任何输入源之外,它还会临时添加一个输入源来监视来自窗口系统的事件,包括鼠标和按键事件。

  1. 还有其他为弹出窗口实现事件循环的好例子吗?更好的是内置的伪代码 NSApplication 运行循环。

有旧的 Apple 示例代码,实际上是他们对 GLUT 的实现。它提供a subclass of NSApplication 并覆盖-run 方法。当你去掉一些只与应用程序启动或 GLUT 相关的东西时,它非常简单。这只是-nextEventMatchingMask:...-sendEvent: 的循环。

【讨论】:

  • 您好,Ken,感谢您的详细回答。为了澄清起见,我编辑了原始帖子以包含我的意思的那种弹出窗口的屏幕截图 - 基本上是一个像菜单一样的小弹出窗口 - 而不是对话框窗口。我想我开始理解这一切是如何联系在一起的,并且可能只是通过自己的运行循环来实现。再次感谢
  • 查看我在顶部附近的编辑以获取有关实现自定义弹出菜单样式窗口的示例代码和视频说明。
  • 谢谢肯。非常有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-24
  • 2012-03-07
  • 1970-01-01
相关资源
最近更新 更多