0

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

AVJ
  • 213
  • 3
  • 11
  • Events and signals/slots are two different mechanisms, don't mix them up! – Kuba hasn't forgotten Monica Jun 23 '15 at 20:06
  • @KubaOber, my application is a non-interactive UI; it's read only. No events, no nothing, just displaying of 3 text values. So I have no user-generated events anyway. My values are updated in main and are (supposed to be) displayed in the UI object. Apart from that there is no need for slots/signals. Should I be converting my setter methods to slots and connecting to them from the main via a signal (that should be generated when the values change)? – AVJ Jun 23 '15 at 20:26
  • Interactivity here is a synonym for "it works and doesn't appear dead". It doesn't imply anything about whether the user can interact with it. The interaction is between the application and the operating system. It processes plenty of events internally, but that's hidden from you. If you stop processing those events, the application won't work anymore. – Kuba hasn't forgotten Monica Jun 23 '15 at 20:57
  • Please add a qt4 and/or qt5 tags depending on what Qt major versions you're limited to. This will affect the kind of example code you get in answers (if any). – Kuba hasn't forgotten Monica Jun 23 '15 at 21:03

2 Answers2

1

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

madduci
  • 2,635
  • 1
  • 32
  • 51
  • So should my processing code be in the main function or in the actual QApplication/UI/Widget object? I looked at QT documentation and they all have documentation for very specific things (I've been hanging around http://doc.qt.io/ since yesterday but it hasn't helped me). The only documentation page that's somewhat informative to me is the signal/slot method (http://doc.qt.io/qt-4.8/signalsandslots.html). In fact, yesterday I coded up the entire application using signals/slots but I could not access my sensor values to send to the UI hence I gave that up. – AVJ Jun 23 '15 at 20:44
  • @Anshul Qt comes with lots and lots of example code. Look at that. You can also look at [my answers](http://stackoverflow.com/search?q=user%3A1329652+%5Bqt%5D) as many of them contain self-contained examples. All of that code is in [a github repository](https://github.com/KubaO/stackoverflown). – Kuba hasn't forgotten Monica Jun 23 '15 at 21:06
0

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"
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313