I'm attempting to implement a simple, lightweight system for recording Qt GUI events and playing them back from a script. 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 GUI事件并从脚本中回放它们。我认为使用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. (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.)
我只是在一个worker(非gui)线程中执行上面的脚本。(我不能使用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正在运行的同一个eventloop中。
- When the special event is handled:
- Call
QApplication.processEvents()
- 调用QApplication.processEvents()
- Set a flag that tells the playback thread it's okay to continue
- 设置一个标志,告诉回放线程它可以继续
- Call
- 处理特殊事件时:调用QApplication.processEvents()设置一个标志,该标志告诉回放线程可以继续
- Post a special event to the same eventloop that
- 等待所有事件完成处理:** *将一个特殊事件发送到obj正在运行的同一个eventloop中。处理特殊事件时:调用QApplication.processEvents()设置一个标志,该标志告诉回放线程可以继续
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
.
整个系统似乎对鼠标事件、关键事件等都可以正常工作。但是当我尝试为我的主QMenuBar回放事件时,QAction处理器有问题。
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. As far as I can tell, QApplication.processEvents()
is returning before the QAction
handler is complete.
无论我做什么尝试,似乎都无法强制我的回放线程阻塞以完成所有QAction。单击我的QMenu条目时触发的处理程序。就我所知,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()
? I need a way to block for the completion of my QMenu
's QAction
handlers.
QMenu widgets或QAction信号是否有什么特殊之处,它们违反了QApplication.postEvent()和/或QApplication.processEvents()的常规规则?我需要一种方法来阻止QMenu的QAction处理程序的完成。
[*] Not every event is recorded. I only record spontaneous()
events, and I also filter out a few other types (e.g. Paint
events and ordinary mouse movements).
不是每个事件都被记录下来。我只记录自发事件,我也过滤掉一些其他类型的事件(例如,绘制事件和普通的鼠标移动)。
[**] This is important because the next event in the script might refer to a widget that was created by the previous event.
这一点很重要,因为脚本中的下一个事件可能引用前一个事件创建的小部件。
2 个解决方案
#1
1
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). Basically, the Qt Event handling system does NOT necessarily handle events in the order they're posted. 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.
我认为您的问题最好通过使用QFuture和QFutureWatcher(也就是说,如果您使用的是线程的QtConcurrent名称空间,而不是QThreads)来解决。基本上,Qt事件处理系统并不一定按照事件发布的顺序来处理事件。如果您需要阻塞,直到某个操作完成,并且您正在一个单独的线程中执行该操作,那么您可以使用QtConcurrent::run()与QFutureWatcher一起返回的QFuture对象来阻塞,直到特定的线程完成其处理。
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. Behind the scenes, Qt can reorder and compress these events to save processor time. I suspect this is more your problem.
另外要考虑的是处理事件的方式。当您使用QApplication.postEvent()时,您创建的事件将被添加到稍后要处理的接收者事件队列中。在幕后,Qt可以重新排序和压缩这些事件,以节省处理器时间。我怀疑这更多的是你的问题。
In your function which handles playback, consider using QCoreApplication::processEvents(), which will not return until all events have finished processing. Documentation for QCoreApplication is here.
在处理回放的函数中,请考虑使用QCoreApplication: processEvents(),它在所有事件完成处理后才返回。QCoreApplication的文档在这里。
#2
1
QMenu widgets and QAction signals are a special case. QMenu has an exec() function, normally used for popups. I suspect (but I don't know for sure) that QMenuBar would use this mechanism when it opens a regular pull-down menu. 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? 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.
QMenu widget和QAction信号是一个特例。QMenu有一个exec()函数,通常用于弹出窗口。我怀疑(但我不确定)QMenuBar会在打开常规下拉菜单时使用这种机制。这些文档并不清楚,但是菜单的作用很像对话框,因为它们阻止了所有其他的用户活动——除了提供自己的事件循环菜单之外,Qt是如何做到这一点的?我无法填满你文章中所有信息的空白,但我看不出你的回放线程如何处理新的事件循环。
#1
1
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). Basically, the Qt Event handling system does NOT necessarily handle events in the order they're posted. 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.
我认为您的问题最好通过使用QFuture和QFutureWatcher(也就是说,如果您使用的是线程的QtConcurrent名称空间,而不是QThreads)来解决。基本上,Qt事件处理系统并不一定按照事件发布的顺序来处理事件。如果您需要阻塞,直到某个操作完成,并且您正在一个单独的线程中执行该操作,那么您可以使用QtConcurrent::run()与QFutureWatcher一起返回的QFuture对象来阻塞,直到特定的线程完成其处理。
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. Behind the scenes, Qt can reorder and compress these events to save processor time. I suspect this is more your problem.
另外要考虑的是处理事件的方式。当您使用QApplication.postEvent()时,您创建的事件将被添加到稍后要处理的接收者事件队列中。在幕后,Qt可以重新排序和压缩这些事件,以节省处理器时间。我怀疑这更多的是你的问题。
In your function which handles playback, consider using QCoreApplication::processEvents(), which will not return until all events have finished processing. Documentation for QCoreApplication is here.
在处理回放的函数中,请考虑使用QCoreApplication: processEvents(),它在所有事件完成处理后才返回。QCoreApplication的文档在这里。
#2
1
QMenu widgets and QAction signals are a special case. QMenu has an exec() function, normally used for popups. I suspect (but I don't know for sure) that QMenuBar would use this mechanism when it opens a regular pull-down menu. 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? 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.
QMenu widget和QAction信号是一个特例。QMenu有一个exec()函数,通常用于弹出窗口。我怀疑(但我不确定)QMenuBar会在打开常规下拉菜单时使用这种机制。这些文档并不清楚,但是菜单的作用很像对话框,因为它们阻止了所有其他的用户活动——除了提供自己的事件循环菜单之外,Qt是如何做到这一点的?我无法填满你文章中所有信息的空白,但我看不出你的回放线程如何处理新的事件循环。