如何监控任意小部件的更改?

时间:2013-12-23 06:08:26

标签: qt events xorg

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

每次有意义的GUI更新时,我都需要运行一个函数,同时最好忽略按下按钮和释放按钮之类的东西。有没有办法创建一个钩子而没有子类化我将使用的每一个Qt Widget?我知道的唯一方法是覆盖所有内容的paintEvent。我想要一个更简单的解决方案。

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

1 个答案:

答案 0 :(得分:1)

下面的代码就是这么做的。它没有深入挖掘Qt的内部,它只是利用了后备存储设备通常是QImages的事实。它可以被修改以适应基于OpenGL的后备存储。

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

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

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

在您的应用程序中,目标可以是一个对象,它获取QImage内容,将它们转换为灰度,适当调整大小并将它们传递给您的设备。

如果无法正常处理更新,我不太明白激光设备的工作原理。我认为它是一个光栅扫描激光器,它在一个看起来像这样的循环中连续运行:

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

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

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

您要从连接到newImage的通知槽中翻转WidgetMonitor标记。您可能会发现在设备驱动程序代码中利用QImage和Qt的一般功能将使开发更容易。 Qt提供便携式计时器,线程,集合等。我假设您的“驱动程序”完全是用户空间,并通过串行端口或以太网与实际控制激光设备的微控制器进行通信。

如果您要为激光设备编写内核驱动程序,那么界面可能非常相似,除非您最终将图像位图写入打开的设备句柄。

screenshot

// 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"