QT non-interactive UI value updation

334 views Asked by At

I have designed a very simple qt application (UI widget) that displays 3 floating point values in text boxes. There is one main function (the likes of the QT beginner tutorial) doing all the processing and a QMainWindow class with 3 text boxes, that are set through setter methods in this QMainWindow; these methods are called from the main function and I simply pass the sensor readings. All it's supposed to do is keep updating the values on the UI as they change in the background process. This background process is a c++ project executable that deals with the sensor hardware, and is called by a QProcess object; this executable couts 3 numbers constantly at around 20 Hz (these are sensor readouts and they keep fluctuating). I have all the logic working correctly in the main function in the QApplication, and I can qDebug() and see that the proper values are being outputted.

My only problem is updating the value displayed in the UI widget. I started working on QT yesterday, so I'm new to it, and I read about slots and signals, but I don't really need event handling. I just need the textEdit values to get updated.

The actual value processing is done in a sensor-read-processing-loop; it's a infinite while loop that will keep spewing values from the executable until I escape the loop:

1) If I call the app.exec() in this while loop (which sounds like a really bad idea at first because I thought it'll keep creating new windows), nothing happens; the UI shows up, the values keep getting generated in the background but don't show up in the UI.

2) If I call app.exec() before the while loop, which is the proper way, no values show up in the UI, but keep getting generated in the background by the executable (similar to (1)). This is my real problem; how do I refresh the values in the UI AFTER calling the app.exec()?

3)If I call app.exec() after this loop, it will display only one set of values since I've already escaped the loop.

To that end I've read stuff on google about event handling using slots and signals (most forums recommend that). Although I don't really have a complicated application with multiple objects. I've also tried using pointers but after I got through all the runtime exceptions, the values still don't get updated. I won't be able to post the code here because the code is on an embedded device and it's not able to connect to the internet currently.

I'm using process.start(programPath, arguments) to start the process, and p.waitForReadyRead() and p.readLine() to read data off the console output of the executable Is there any simple think that'll let me do this. Thanks in advance

2

There are 2 answers

0
Kuba hasn't forgotten Monica On BEST ANSWER

As a novice, it helps to forget about any and all methods named waitFor.... They block, and in a gui application this generally makes things not work. You don't need these methods, so don't use them.

You need to react when new input is available from QProcess. Below is a simple stand-alone example for Qt 5 and C++11.

If the application is invoked with any command line arguments, it acts as a generator to simulate your data source process. When invoked without arguments, it will start itself in the emulation mode, and display the user interface that reflects the incoming data in real time.

There are several points worth mentioning:

  1. It is a single-file example, and it invokes itself once :)
  2. There isn't a single explicit memory allocation. Manual memory management should be shunned. The proper lifetime of all objects is handled by the C++11's and Qt's semantics.
  3. QCoreApplication::applicationFilePath() is used to refer to the executable.
  4. When the process indicates that new data is available, we read any data as long as a full line can be read. As long as you use readLine, you must do it this way. QProcess can indicate data in any chunks - there's no guarantee that any number of lines is available. When readyRead is emitted, you can see any number of lines, including zero! All that readyRead means is that there's at least one byte of data to read. That's all.
  5. Lambdas are used to keep the code concise. Qt 4-style code would need at least one QObject-derived class to provide the necessary slots.
  6. To properly shut down the process, the application is made not to quit when the last window is closed - it would by default. When the last window is closed, the process is requested to terminate, and when it finishes, the application quits.
  7. Again, this is C++11 code that is about as concise as Python or other high-level scripting languages would be. That's what Qt brings with C++11. It should not read like C would, it's not supposed to :)

screenshot of the example

#include <QApplication>
#include <QGridLayout>
#include <QProcess>
#include <QLabel>
#include <QTimer>
#include <QTextStream>
#include <QRegExp>
#include <cstdio>

// QT 5, C++11
int main(int argc, char *argv[])
{
   if (argc > 1) {
      QCoreApplication app(argc, argv);
      // output 3 random values per line at ~20Hz
      QTextStream out(stdout);
      QTimer timer;
      timer.start(50);
      QObject::connect(&timer, &QTimer::timeout, [&out]{
         out << qrand() << " " << qrand() << " " << qrand() << endl;
      });
      return app.exec();
   }
   QApplication app(argc, argv);
   QWidget w;
   QGridLayout layout(&w);
   QLabel l1, l2, l3;
   layout.addWidget(&l1, 0, 0);
   layout.addWidget(&l2, 0, 1);
   layout.addWidget(&l3, 0, 2);
   QProcess process;
   process.start(QCoreApplication::applicationFilePath(), QStringList("foo"));
   QObject::connect(&process, &QProcess::readyRead, [&]{
      static QRegExp sep("\\W+");
      while (process.canReadLine()) {
         QStringList data = QString::fromLocal8Bit(process.readLine()).split(sep, QString::SkipEmptyParts);
         if (data.length() != 3) continue;
         l1.setText(data.at(0));
         l2.setText(data.at(1));
         l3.setText(data.at(2));
      }
   });
   app.setQuitOnLastWindowClosed(false);
   process.connect(&app, SIGNAL(lastWindowClosed()), SLOT(terminate()));
   app.connect(&process, SIGNAL(finished(int)), SLOT(quit()));
   w.show();
   return app.exec();
}
#include <QApplication>
#include <QGridLayout>
#include <QProcess>
#include <QLabel>
#include <QTimer>
#include <QTextStream>
#include <QRegExp>
#include <QPointer>
#include <cstdio>

// QT 4, C++98
class Emulator : public QObject {
   Q_OBJECT
   QTextStream m_out;
   QTimer m_timer;
   Q_SLOT void on_timeout() {
      m_out << qrand() << " " << qrand() << " " << qrand() << endl;
   }
public:
   Emulator() : m_out(stdout) {
      m_timer.start(50);
      connect(&m_timer, SIGNAL(timeout()), SLOT(on_timeout()));
   }
};

class Widget : public QWidget {
   Q_OBJECT
   QGridLayout m_layout;
   QLabel m_l1, m_l2, m_l3;
   QPointer<QProcess> m_process;
   Q_SLOT void on_readyRead() {
      static QRegExp sep("\\W+");
      while (m_process->canReadLine()) {
         QStringList data = QString::fromLocal8Bit(m_process->readLine()).split(sep, QString::SkipEmptyParts);
         if (data.length() != 3) continue;
         m_l1.setText(data.at(0));
         m_l2.setText(data.at(1));
         m_l3.setText(data.at(2));
      }
   }
public:
   Widget(QProcess * process) : m_layout(this), m_process(process) {
      m_layout.addWidget(&m_l1, 0, 0);
      m_layout.addWidget(&m_l2, 0, 1);
      m_layout.addWidget(&m_l3, 0, 2);
      connect(m_process, SIGNAL(readyRead()), SLOT(on_readyRead()));
   }
};

int main(int argc, char *argv[])
{
   if (argc > 1) {
      // output 3 random values per line at ~20Hz
      QCoreApplication app(argc, argv);
      Emulator emulator;
      return app.exec();
   }
   QApplication app(argc, argv);
   QProcess process;
   Widget w(&process);
   process.start(QCoreApplication::applicationFilePath(), QStringList("foo"));
   app.setQuitOnLastWindowClosed(false);
   process.connect(&app, SIGNAL(lastWindowClosed()), SLOT(terminate()));
   app.connect(&process, SIGNAL(finished(int)), SLOT(quit()));
   w.show();
   return app.exec();
}
#include "main.moc"
2
madduci On

Qt uses an MVC approach foor the GUI. Signal/slots trigger the update of the view, while your Model (your textEdit) got updated by a controller (your class, another component). You should use always signal/slots to have a responsive GUI and better handling of your application