简体   繁体   English

Qt GUI事件录制和播放

[英]Qt GUI event recording and playback

I'm attempting to implement a simple, lightweight system for recording Qt GUI events and playing them back from a script. 我正在尝试实现一个简单,轻量级的系统来记录Qt GUI事件并从脚本中播放它们。 I thought this would be fairly straightforward using the magic of Qt's event system, but I'm running into a problem I don't understand. 我认为使用Qt事件系统的魔力会相当简单,但我遇到了一个我不明白的问题。

Here's quick summary of what I'm doing: 以下是我正在做的事情的快速摘要:

RECORDING: 记录:

I use QApplication.instance().eventFilter() to capture all GUI events I'm interested in* and save them to a Python script, in which each step looks something like this: 我使用QApplication.instance().eventFilter()捕获我感兴趣的所有GUI事件*并将它们保存到Python脚本中,其中每个步骤看起来像这样:

obj = get_named_object('MainWindow.my_menubar')
recorded_event = QMouseEvent(2, PyQt4.QtCore.QPoint(45, 8), 1, Qt.MouseButtons(0x1), Qt.KeyboardModifiers(0x0))
post_event(obj, recorded_event)

PLAYBACK: 回放:

I simply execute the script above, in a worker ( non-GUI ) thread. 我只是在一个worker( 非GUI )线程中执行上面的脚本。 (I can't use the GUI thread because I want to keep sending scripted events to the application, even if the 'main' eventloop is blocked while a modal dialog eventloop is running.) (我不能使用GUI线程,因为我想继续向应用程序发送脚本事件,即使在模式对话框eventloop运行时'main'eventloop被阻止。)

The important stuff happens in my post_event() function, which needs to do two things: 重要的东西发生在我的post_event()函数中,它需要做两件事:

  • First, call QApplication.postEvent(obj, recorded_event) 首先,调用QApplication.postEvent(obj, recorded_event)
  • Wait for all events to finish processing:** 等待所有事件完成处理:**
    • Post a special event to the same eventloop that obj is running in. 将特殊事件发布到运行obj的同一事件循环中。
    • When the special event is handled: 处理特殊事件时:
      • Call QApplication.processEvents() 调用QApplication.processEvents()
      • Set a flag that tells the playback thread it's okay to continue 设置一个标志,告诉回放线程可以继续

After the second part is complete, my expectation is that all effects of the first part (the recorded event) have completed , since the special event was queued after the recorded event. 第二部分完成后,我的期望是第一部分(记录的事件)的所有效果都已完成 ,因为特殊事件在记录的事件之后排队。

The whole system mostly seems to work just fine for mouse events, key events, etc. But I'm having a problem with QAction handlers when I attempt to playback events for my main QMenuBar . 整个系统主要是似乎只是正常工作的鼠标事件,重要事件等,但我有一个问题QAction处理程序,当我试图播放我的主要活动QMenuBar

No matter what I try, it seems that I can't force my playback thread to block for the completion of all QAction.triggered handlers that result from clicking on my QMenu items. 无论我尝试什么,似乎我都不能强制我的回放线程阻止完成因点击我的QMenu项而产生的所有QAction.triggered处理程序 As far as I can tell, QApplication.processEvents() is returning before the QAction handler is complete. 据我所知, QApplication.processEvents()QAction处理程序完成之前返回。

Is there something special about QMenu widgets or QAction signals that breaks the normal rules for QApplication.postEvent() and/or QApplication.processEvents() ? 有关QMenu小部件或QAction信号的特殊内容QMenu违反了QApplication.postEvent()和/或QApplication.processEvents()的常规规则? I need a way to block for the completion of my QMenu 's QAction handlers. 我需要一种方法来阻止我的QMenuQAction处理程序的完成。

[*] Not every event is recorded. [*]并非每个事件都被记录下来。 I only record spontaneous() events, and I also filter out a few other types (eg Paint events and ordinary mouse movements). 我只记录spontaneous()事件,我还过滤掉了一些其他类型(例如Paint事件和普通鼠标移动)。

[**] This is important because the next event in the script might refer to a widget that was created by the previous event. [**]这很重要,因为脚本中的下一个事件可能引用上一个事件创建的窗口小部件。

I think your problem might best be served by using QFuture and QFutureWatcher (that is, if you're using the QtConcurrent namespace for threads, and not QThreads). 我认为你的问题可能最好通过使用QFuture和QFutureWatcher来实现(也就是说,如果你为线程使用QtConcurrent命名空间而不是QThreads)。 Basically, the Qt Event handling system does NOT necessarily handle events in the order they're posted. 基本上,Qt事件处理系统不一定按照发布的顺序处理事件。 If you need to block until a certain action is completed, and you're doing that action in a separate thread, you can use the QFuture object returned by QtConcurrent::run() with a QFutureWatcher to block until that particular thread finishes its processing. 如果需要阻塞直到某个操作完成,并且您在单独的线程中执行该操作,则可以使用QtConcurrent :: run()返回的QFuture对象与QFutureWatcher一起阻塞,直到该特定线程完成其处理。

Something else to consider is the way you handle events. 还需要考虑的是处理事件的方式。 When you use QApplication.postEvent(), the event you create gets added to the receiver's event queue to be handled later. 使用QApplication.postEvent()时,您创建的事件将添加到接收方的事件队列中,以便稍后处理。 Behind the scenes, Qt can reorder and compress these events to save processor time. 在幕后,Qt可以重新排序和压缩这些事件以节省处理器时间。 I suspect this is more your problem. 我怀疑这更像是你的问题。

In your function which handles playback, consider using QCoreApplication::processEvents(), which will not return until all events have finished processing. 在处理回放的函数中,请考虑使用QCoreApplication :: processEvents(),它将在所有事件处理完毕后才返回。 Documentation for QCoreApplication is here. QCoreApplication的文档就在这里。

QMenu widgets and QAction signals are a special case. QMenu小部件和QAction信号是一个特例。 QMenu has an exec() function, normally used for popups. QMenu有一个exec()函数,通常用于弹出窗口。 I suspect (but I don't know for sure) that QMenuBar would use this mechanism when it opens a regular pull-down menu. 我怀疑(但我不确定)QMenuBar在打开常规下拉菜单时会使用此机制。 The docs are not clear about this, but Menus act a lot like dialog boxes in that they block all other user activity - how would Qt do this except by giving menus their own event loop? 文档对此并不清楚,但Menus的行为很像对话框,因为它们阻止了所有其他用户活动 - 除了通过给菜单提供自己的事件循环之外,Qt将如何做到这一点? I can't fill in all the blanks from the information in your post, but I don't see how your playback thread would cope with a new event loop. 我无法填写你帖子中信息的所有空白,但我看不到你的播放线程如何应对新的事件循环。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM