如何通过向 Qt 事件系统发送事件来模拟鼠标点击?
How can I simulate mouse clicks by posting events to the Qt event system?
我想对我的 Qt 应用程序进行基本的自动化测试。它记录鼠标事件并将它们写入文件 (f.e.mousepress(300, 400))。启动自动化时,它会从文件中读取坐标,发送适当的鼠标事件,并与之前保存的屏幕截图进行像素比较。
目前,我有一个覆盖应用程序的覆盖小部件,并且具有透明的鼠标事件。它所做的只是跟踪坐标。
当读回数据时,该叠加层会在鼠标按下的位置绘制一个矩形。
在将 mousePressEvents 发送到 Qt 的事件系统时我需要帮助。它在正确的位置绘制点,但实际上从未进行过物理点击。
有没有办法用 Qt 做到这一点,或者我必须使用 Window 的 SendInput()?
有没有办法暂停并等待鼠标事件结束?我需要知道事件何时完成才能开始逐像素比较。
Widget::Widget( QWidget *parent )
: QFrame( parent )
, mPoint(QPoint(0,0))
{
setWindowFlags(Qt::WindowStaysOnTopHint);
setStyleSheet("background-color: rgba(0, 0,255, 2%);");
setAttribute(Qt::WA_TransparentForMouseEvents, true);
setGeometry(parent->geometry());
...
}
void Widget::run()
{
QFile file( "coordinates.txt", NULL );
if(!file.open( QIODevice::ReadOnly ))
return;
QTextStream in(&file);
int i = 0;
while (!in.atEnd())
{
QString line = in.readLine();
if(line.startsWith("mousepress"))
{
int startIndex = line.indexOf('(');
int endIndex = line.indexOf(')');
QString coord = line.mid(startIndex+1, line.size() - startIndex - 2);
QStringList nbr = coord.split(',');
mPoint = QPoint(nbr[0].toInt(), nbr[1].toInt());
QWidget *receiver = QApplication::widgetAt(mPoint);
QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonPress, mPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::postEvent(receiver, event); // same result with sendEvent()
QCoreApplication::processEvents();
update();
// wait till the event finished, then continue with next point
}
}
}
void Widget::paintEvent(QPaintEvent *event)
{
QPainter p( this );
QPen pen;
pen.setBrush(Qt::NoBrush);
if(!mPoint.isNull())
{
pen.setColor(Qt::red);
pen.setWidth( 2 );
p.setPen(pen);
p.drawRoundRect(mPoint.x(), mPoint.y(), 10, 10, 25, 25);
p.drawText(mPoint, QString::number(mPoint.x()) + ", " + QString::number(mPoint.y()));
}
}
[已编辑]
我听从了 ddriver 的建议,经过一些更改后它起作用了:我在文件中保存全局和本地位置,以发送到 QMouseEvent。
在进行屏幕截图并将其与保存的图像进行比较之前,如何确定鼠标单击已完成?
void Widget::DoStep()
{
if(!mInStream.atEnd())
{
QString line = mInStream.readLine();
if(line.startsWith("MouseButtonPress"))
{
QPoint pos = parseGlobalPos();
QPoint localPos = parseLocalPos();
QWidget *receiver = QApplication::widgetAt(pos);
QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonPress,localPos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QApplication::postEvent(receiver, event);
QMouseEvent *eventRelease = new QMouseEvent(QEvent::MouseButtonRelease, localPos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QApplication::postEvent(receiver, eventRelease);
// after successful click, take screenshot and compare them
}
}
if (!mInStream.atEnd())
QMetaObject::invokeMethod(this, "DoStep", Qt::QueuedConnection);
else
file.close();
}
尝试将事件发送到 QGraphicsView
的视口:
qApp->sendEvent(view->viewport(), &mousePressEvent);
如果我理解正确的话,它的根源是阻塞 while
循环,它阻塞了线程并且不允许事件循环旋转和处理事件。没有办法 "pause" 因为那样也会阻塞事件循环,它也无法完成工作,但是有一种方法可以将工作拆分为一次完成一个步骤, 一次一个事件循环。
解决方案是不使用阻塞 while
循环,而是实施事件系统驱动的循环。然后你在每个事件循环周期处理一行,直到你 运行 失业。
将文件、流和所有这些东西移到函数之外,使它们成为 class 成员,以便它们持久存在。然后在 run()
中,您只需设置阅读输入,并将所有事件发布内容移动到一个新功能,例如 doStep()
,在 run()
中,设置后您将拥有:
QMetaObject::invokeMethod(this, "doStep", Qt::QueuedConnection);
在doStep()
中,除了活动的东西,最后你还要安排:
if (!in.atEnd()) QMetaObject::invokeMethod(this, "doStep", Qt::QueuedConnection);
else // we are done, close file, cleanup, whatever
这样每个事件循环周期只执行一个步骤,允许事件循环旋转并处理事件。当有工作时,将为下一个事件循环周期安排一个步骤。您还可以使用单次计时器来执行此操作,甚至可以使用排队连接,您可以通过发出重载事件来触发它以在完成时发出信号。您不需要也不应该强制处理事件,使用这种方法就没有必要了。
一般的概念是关于非阻塞异步事件驱动的自调度工作器,我已经发布了一个完全实现的 。好处是您可以跟踪进度、暂停、取消以及所有这些好东西。
我想对我的 Qt 应用程序进行基本的自动化测试。它记录鼠标事件并将它们写入文件 (f.e.mousepress(300, 400))。启动自动化时,它会从文件中读取坐标,发送适当的鼠标事件,并与之前保存的屏幕截图进行像素比较。
目前,我有一个覆盖应用程序的覆盖小部件,并且具有透明的鼠标事件。它所做的只是跟踪坐标。 当读回数据时,该叠加层会在鼠标按下的位置绘制一个矩形。 在将 mousePressEvents 发送到 Qt 的事件系统时我需要帮助。它在正确的位置绘制点,但实际上从未进行过物理点击。 有没有办法用 Qt 做到这一点,或者我必须使用 Window 的 SendInput()?
有没有办法暂停并等待鼠标事件结束?我需要知道事件何时完成才能开始逐像素比较。
Widget::Widget( QWidget *parent )
: QFrame( parent )
, mPoint(QPoint(0,0))
{
setWindowFlags(Qt::WindowStaysOnTopHint);
setStyleSheet("background-color: rgba(0, 0,255, 2%);");
setAttribute(Qt::WA_TransparentForMouseEvents, true);
setGeometry(parent->geometry());
...
}
void Widget::run()
{
QFile file( "coordinates.txt", NULL );
if(!file.open( QIODevice::ReadOnly ))
return;
QTextStream in(&file);
int i = 0;
while (!in.atEnd())
{
QString line = in.readLine();
if(line.startsWith("mousepress"))
{
int startIndex = line.indexOf('(');
int endIndex = line.indexOf(')');
QString coord = line.mid(startIndex+1, line.size() - startIndex - 2);
QStringList nbr = coord.split(',');
mPoint = QPoint(nbr[0].toInt(), nbr[1].toInt());
QWidget *receiver = QApplication::widgetAt(mPoint);
QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonPress, mPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::postEvent(receiver, event); // same result with sendEvent()
QCoreApplication::processEvents();
update();
// wait till the event finished, then continue with next point
}
}
}
void Widget::paintEvent(QPaintEvent *event)
{
QPainter p( this );
QPen pen;
pen.setBrush(Qt::NoBrush);
if(!mPoint.isNull())
{
pen.setColor(Qt::red);
pen.setWidth( 2 );
p.setPen(pen);
p.drawRoundRect(mPoint.x(), mPoint.y(), 10, 10, 25, 25);
p.drawText(mPoint, QString::number(mPoint.x()) + ", " + QString::number(mPoint.y()));
}
}
[已编辑]
我听从了 ddriver 的建议,经过一些更改后它起作用了:我在文件中保存全局和本地位置,以发送到 QMouseEvent。
在进行屏幕截图并将其与保存的图像进行比较之前,如何确定鼠标单击已完成?
void Widget::DoStep()
{
if(!mInStream.atEnd())
{
QString line = mInStream.readLine();
if(line.startsWith("MouseButtonPress"))
{
QPoint pos = parseGlobalPos();
QPoint localPos = parseLocalPos();
QWidget *receiver = QApplication::widgetAt(pos);
QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonPress,localPos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QApplication::postEvent(receiver, event);
QMouseEvent *eventRelease = new QMouseEvent(QEvent::MouseButtonRelease, localPos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QApplication::postEvent(receiver, eventRelease);
// after successful click, take screenshot and compare them
}
}
if (!mInStream.atEnd())
QMetaObject::invokeMethod(this, "DoStep", Qt::QueuedConnection);
else
file.close();
}
尝试将事件发送到 QGraphicsView
的视口:
qApp->sendEvent(view->viewport(), &mousePressEvent);
如果我理解正确的话,它的根源是阻塞 while
循环,它阻塞了线程并且不允许事件循环旋转和处理事件。没有办法 "pause" 因为那样也会阻塞事件循环,它也无法完成工作,但是有一种方法可以将工作拆分为一次完成一个步骤, 一次一个事件循环。
解决方案是不使用阻塞 while
循环,而是实施事件系统驱动的循环。然后你在每个事件循环周期处理一行,直到你 运行 失业。
将文件、流和所有这些东西移到函数之外,使它们成为 class 成员,以便它们持久存在。然后在 run()
中,您只需设置阅读输入,并将所有事件发布内容移动到一个新功能,例如 doStep()
,在 run()
中,设置后您将拥有:
QMetaObject::invokeMethod(this, "doStep", Qt::QueuedConnection);
在doStep()
中,除了活动的东西,最后你还要安排:
if (!in.atEnd()) QMetaObject::invokeMethod(this, "doStep", Qt::QueuedConnection);
else // we are done, close file, cleanup, whatever
这样每个事件循环周期只执行一个步骤,允许事件循环旋转并处理事件。当有工作时,将为下一个事件循环周期安排一个步骤。您还可以使用单次计时器来执行此操作,甚至可以使用排队连接,您可以通过发出重载事件来触发它以在完成时发出信号。您不需要也不应该强制处理事件,使用这种方法就没有必要了。
一般的概念是关于非阻塞异步事件驱动的自调度工作器,我已经发布了一个完全实现的