如何监视对任意窗口小部件的更改? [英] How to monitor changes to an arbitrary widget?

查看:88
本文介绍了如何监视对任意窗口小部件的更改?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在启动一个基于Qt小部件的,具有相当复杂设计的QT5应用程序.它在带有触摸屏的Beagleboard上运行.我将有一个相当奇怪的本地发明,而不是LCD显示器.这是在丙烯酸板上的激光绘画.还没有驱动程序.要实际更新屏幕,我必须将窗口的屏幕截图创建为位图,将其转换为灰度并馈入可处理激光的专有库.准备好后,它看起来应该很可爱.不幸的是,激光灯会在更新时闪烁,所以我不能只在计时器上截图,否则会像地狱般生涩.

I am starting a QT5 application with a rather complex design based on Qt Widgets. It runs on Beagleboard with a touchscreen. I will have a rather weird local invention instead of the LCD display. It's a laser painting on acrylic plate. It has no driver yet. To actually update a screen I must create a screenshot of the window as bitmap, turn it to grayscale and feed to a proprietary library, which will handle the laser. It should look cute, when ready. Unfortunately, the laser blinks on update, so I cannot just make screenshots on timer, or it will be jerky like hell.

每次需要进行有意义的GUI更新时,我都需要运行一个函数,同时最好忽略诸如按下和释放按钮之类的事情.有没有一种方法可以创建一个钩子而不将我将要使用的每个Qt Widget子类化?我知道的唯一方法是重写所有内容.我想要一个更简单的解决方案.

I need to run a function every time a meaningful update of GUI happens, while preferably ignore things like button being pressed and released. Is there some way to create a hook without subclassing every single Qt Widget I will use? The only way to do this I know is to override paintEvent of everything. I want a simpler solution.

可能的假设是:该应用程序将在带有虚拟显示的X服务器下运行,它将是唯一运行的GUI应用程序.某些更新是在没有用户输入的情况下发生的.

Possible assumptions are: the application will be running under X server with dummy display, will be the only GUI app running. Some updates happen without user input.

推荐答案

下面的代码可以做到这一点.它并没有对Qt的内部进行深入研究,它只是利用了后备存储设备通常是QImages这一事实.可以对其进行修改以适应基于OpenGL的后备存储.

The code below does it. It doesn't dig too deeply into the internals of Qt, it merely leverages the fact that backing store devices are usually QImages. It could be modified to accommodate OpenGL-based backing stores as well.

WidgetMonitor类用于监视窗口小部件的内容更改.无论将哪个特定的小部件传递给monitor(QWidget*)方法,都将监视整个顶级窗口.您只需要在要监视的窗口中为一个窗口小部件调用monitor方法-任何窗口小部件都可以.更改以QImage窗口内容的形式发送出去.

The WidgetMonitor class is used to monitor the widgets for content changes. An entire top-level window is monitored no matter which particular widget is passed to the monitor(QWidget*) method. You only need to call the monitor method for one widget in the window you intend to monitor - any widget will do. The changes are sent out as a QImage of window contents.

该实现将自身作为事件过滤器安装在目标窗口小部件及其所有子级中,并监视重绘事件.它尝试使用零长度计时器合并重绘通知.将自动跟踪孩子的添加和删除.

The implementation installs itself as an event filter in the target window widget and all of its children, and monitors the repaint events. It attempts to coalesce the repaint notifications by using the zero-length timer. The additions and removals of children are tracked automagically.

运行示例时,它将创建两个窗口:源窗口和目标窗口.它们可能重叠,因此您需要将它们分开.调整源窗口的大小时,目标窗口的大小也会相应更改.对源子级的任何更改(时间标签,按钮状态)都会自动传播到目标.

When you run the example, it creates two windows: a source window, and a destination window. They may be overlapped so you need to separate them. As you resize the source window, the size of the destination's rendition of it will also change appropriately. Any changes to the source children (time label, button state) propagate automatically to the destination.

在您的应用程序中,目标可能是一个对象,该对象采用QImage内容,将其转换为灰度,适当调整大小,然后将其传递到您的设备.

In your application, the destination could be an object that takes the QImage contents, converts them to grayscale, resizes appropriately, and passes them to your device.

我不太了解您的激光设备在无法正常处理更新的情况下的工作方式.我认为这是一台光栅扫描激光器,它以一个大致如下所示的循环连续运行:

I do not quite understand how your laser device works if it can't gracefully handle updates. I presume that it is a raster-scanning laser that runs continuously in a loop that looks roughly like this:

while (1) {
  for (line = 0; line < nLines; ++line) {
    drawLine();
  }
}

您需要修改此循环,使其工作如下:

You need to modify this loop so that it works as follows:

newImage = true;
QImage localImage;
while (1) {
  if (newImage) localImage = newImage;
  for (line = 0; line < localImage.height(); ++line) {
    drawLine(line, localImage);
  }
}

您将从连接到WidgetMonitor的通知插槽中翻转newImage标志.您可能会发现,在设备驱动程序代码中充分利用QImage和Qt的功能将使开发变得更加容易. Qt提供了可移植的计时器,线程,集合等.我认为您的驱动程序"完全是用户空间,并通过串行端口或以太网与实际控制激光设备的微控制器通信.

You'd be flipping the newImage flag from the notification slot connected to the WidgetMonitor. You may well find out that leveraging QImage, and Qt's functionality in general, in your device driver code, will make it much easier to develop. Qt provides portable timers, threads, collections, etc. I presume that your "driver" is completely userspace, and communicates via a serial port or ethernet to the micro controller that actually controls the laser device.

如果要为激光设备编写内核驱动程序,则接口可能非常相似,只是最终将图像位图写入打开的设备句柄.

If you will be writing a kernel driver for the laser device, then the interface would be probably very similar, except that you end up writing the image bitmap to an open device handle.

// https://github.com/KubaO/stackoverflown/tree/master/questions/surface-20737882
#include <QtWidgets>
#include <array>

const char kFiltered[] = "WidgetMonitor_filtered";

class WidgetMonitor : public QObject {
   Q_OBJECT
   QVector<QPointer<QWidget>> m_awake;
   QBasicTimer m_timer;
   int m_counter = 0;
   void queue(QWidget *window) {
      Q_ASSERT(window && window->isWindow());
      if (!m_awake.contains(window)) m_awake << window;
      if (!m_timer.isActive()) m_timer.start(0, this);
   }
   void filter(QObject *obj) {
      if (obj->isWidgetType() && !obj->property(kFiltered).toBool()) {
         obj->installEventFilter(this);
         obj->setProperty(kFiltered, true);
      }
   }
   void unfilter(QObject *obj) {
      if (obj->isWidgetType() && obj->property(kFiltered).toBool()) {
         obj->removeEventFilter(this);
         obj->setProperty(kFiltered, false);
      }
   }
   bool eventFilter(QObject *obj, QEvent *ev) override {
      switch (ev->type()) {
         case QEvent::Paint: {
            if (!obj->isWidgetType()) break;
            if (auto *window = static_cast<QWidget *>(obj)->window()) queue(window);
            break;
         }
         case QEvent::ChildAdded: {
            auto *cev = static_cast<QChildEvent *>(ev);
            if (auto *child = qobject_cast<QWidget *>(cev->child())) monitor(child);
            break;
         }
         default:
            break;
      }
      return false;
   }
   void timerEvent(QTimerEvent *ev) override {
      if (ev->timerId() != m_timer.timerId()) return;
      qDebug() << "painting: " << m_counter++ << m_awake;
      for (auto w : m_awake)
         if (auto *img = dynamic_cast<QImage *>(w->backingStore()->paintDevice()))
            emit newContents(*img, w);
      m_awake.clear();
      m_timer.stop();
   }

  public:
   explicit WidgetMonitor(QObject *parent = nullptr) : QObject{parent} {}
   explicit WidgetMonitor(QWidget *w, QObject *parent = nullptr) : QObject{parent} {
      monitor(w);
   }
   Q_SLOT void monitor(QWidget *w) {
      w = w->window();
      if (!w) return;
      filter(w);
      for (auto *obj : w->findChildren<QWidget *>()) filter(obj);
      queue(w);
   }
   Q_SLOT void unMonitor(QWidget *w) {
      w = w->window();
      if (!w) return;
      unfilter(w);
      for (auto *obj : w->findChildren<QWidget *>()) unfilter(obj);
      m_awake.removeAll(w);
   }
   Q_SIGNAL void newContents(const QImage &, QWidget *w);
};

class TestWidget : public QWidget {
   QVBoxLayout m_layout{this};
   QLabel m_time;
   QBasicTimer m_timer;
   void timerEvent(QTimerEvent *ev) override {
      if (ev->timerId() != m_timer.timerId()) return;
      m_time.setText(QTime::currentTime().toString());
   }

  public:
   explicit TestWidget(QWidget *parent = nullptr) : QWidget{parent} {
      m_layout.addWidget(&m_time);
      m_layout.addWidget(new QLabel{"Static Label"});
      m_layout.addWidget(new QPushButton{"A Button"});
      m_timer.start(1000, this);
   }
};

int main(int argc, char **argv) {
   QApplication app{argc, argv};
   TestWidget src;
   QLabel dst;
   dst.setFrameShape(QFrame::Box);
   for (auto *w : std::array<QWidget *, 2>{&dst, &src}) {
      w->show();
      w->raise();
   }
   QMetaObject::invokeMethod(&dst, [&] { dst.move(src.frameGeometry().topRight()); },
                             Qt::QueuedConnection);

   WidgetMonitor mon(&src);
   src.setWindowTitle("Source");
   dst.setWindowTitle("Destination");
   QObject::connect(&mon, &WidgetMonitor::newContents, [&](const QImage &img) {
      dst.resize(img.size());
      dst.setPixmap(QPixmap::fromImage(img));
   });
   return app.exec();
}

#include "main.moc"

这篇关于如何监视对任意窗口小部件的更改?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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