如何通过将事件发布到 Qt 事件系统来模拟鼠标点击? [英] How can I simulate mouse clicks by posting events to the Qt event system?

查看:127
本文介绍了如何通过将事件发布到 Qt 事件系统来模拟鼠标点击?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想对我的 Qt 应用程序进行基本的自动化测试.它记录鼠标事件并将它们写入文件(例如 mousepress(300, 400)).启动自动化时,它会从文件中读取坐标,发送适当的鼠标事件,并与之前保存的屏幕截图进行像素比较.

I would like to do a rudimentary automation test of my Qt application. It records mouse events and writes them to a file (f.e. mousepress(300, 400)). When starting the automation, it reads the coordinates from the file, sends the appropriate mouse events and would do a pixel comparison with a previously saved screenshot.

目前,我有一个覆盖应用程序并具有透明鼠标事件的覆盖小部件.它所做的就是跟踪坐标.当读回数据时,该叠加层会在鼠标按下位置绘制一个矩形.将 mousePressEvents 发送到 Qt 的事件系统时需要帮助.它在正确的位置绘制点,但实际上从未进行过物理点击.有没有办法用 Qt 做到这一点,还是我必须使用 Window 的 SendInput()?

Currently, I have an overlay widget that spans the app and has transparent mouse events. All it does is track the coordinates. When reading the data back in, that overlay paints a rectangle on the mouse press location. I need help when sending mousePressEvents to Qt's event system. It draws the points on the correct location but never actually does the physical click. Is there a way to do this with Qt or would I have to use Window's SendInput()?

有没有办法暂停并等待鼠标事件完成?我需要知道事件何时完成才能开始逐像素比较.

Is there a way to pause and wait until the mouse event has finished? I would need to know when the event is complete in order to start the pixel by pixel comparison.

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.

I followed ddriver's suggestion and it works after a few changes: I save global and local positions in the file, to send to the QMouseEvent.

在进行屏幕截图并将其与保存的图像进行比较之前,我如何确定鼠标单击已完成?

How could I be sure that the mouse click is complete before doing a screenshot and comparing it to a saved image?

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();
}

推荐答案

如果我理解正确的话,它的根源是阻塞的 while 循环,它阻塞了线程并且不允许事件循环以旋转和处理事件.没有办法暂停",因为这也会阻塞事件循环,并且它也无法完成工作,但是有一种方法可以一次一步地拆分工作,一个一次事件循环.

If I understand the problem correctly, its source is the blocking while loop, which blocks the thread and doesn't allow the event loop to spin and process events. There is no way to "pause" as that would block the event loop as well, and it wouldn't be able to do the work either, but there is a way to split the work to be done one step at a time, one event loop cycle at a time.

解决方案是不使用阻塞的 while 循环,而是实现一个事件系统驱动的循环.然后你在每个事件循环周期处理一行,直到你用完工作.

The solution is to not use a blocking while loop but implement an event system driven loop. Then you process one line on every event loop cycle until you run out of work.

将文件、流和所有这些东西移到函数之外,使它们成为类成员,以便它们持久化.然后在 run() 中,您只需设置用于读取的输入,并将所有事件发布内容移动到一个新函数中,例如 doStep(),并在 中run() 设置后你有一个:

Move the file, stream and all that stuff outside of the function, make them class members so they persist. Then in run() you simply setup the input for reading, and move all the event posting stuff to a new function, for example doStep(), and in run() after you setup you have a:

QMetaObject::invokeMethod(this, "doStep", Qt::QueuedConnection);

doStep()中,除了事件之外,最后你还要做调度:

In doStep(), aside from the event stuff, at the end you also do scheduling:

if (!in.atEnd()) QMetaObject::invokeMethod(this, "doStep", Qt::QueuedConnection);
else // we are done, close file, cleanup, whatever

这种方式每个事件循环周期只执行一个步骤,允许事件循环旋转和处理事件.当有工作时,将为下一个事件循环周期安排一个步骤.您还可以使用单次计时器来执行此操作,甚至可以使用排队连接,您可以通过发出重载事件以在完成时发出信号来触发.您不会也不应该强制处理事件,使用这种方法将没有必要.

This way only one step will be executed per event loop cycle, allowing the event loop to spin and process the events. While there is work, a step will be scheduled for the next event loop cycle. You can also use a single shot timer to do this, or even a queued connection, which you can trigger by emitting overloading the event to emit a signal on completed. You do not, and should not force events to be processed, using this approach there will be no need to.

这个概念一般是关于非阻塞异步事件驱动的自调度工作者,我已经发布了一个完全实现的此处.好处是您可以跟踪进度、暂停、取消和所有好东西.

The concept in general is about a non-blocking async event driven self scheduling worker, I have posted a fully implemented one here. The upsides are you can track progress, pause, cancel and all that good stuff.

这篇关于如何通过将事件发布到 Qt 事件系统来模拟鼠标点击?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆