0

I have been reading a lot about this, but I don't fully get how to go about it (e.g. see How to avoid Qt app.exec() blocking main thread).

The following code is a very naievely written minimal example of what I'm trying to achieve. For the sake of this example: there is a terminal in which you put exam grades for the same subject for one student. Concretely: how a student did for his/her English course over the whole year. Every time you put in the grade of a student, a Qt chart updates and shows the progression.

This is the code I wrote:

#include <iostream>
#include <vector>

#include <QApplication>
#include <QMainWindow>
#include <QChartView>
#include <QLineSeries>

int main(int argc, char **argv)
{
  QApplication app(argc, argv);

  std::vector<int> examScores;
  int totalExams = 5;

  QtCharts::QLineSeries *series = new QtCharts::QLineSeries();
  QtCharts::QChart *chart = new QtCharts::QChart();
  chart->legend()->hide();
  chart->createDefaultAxes();
  QtCharts::QChartView *chartView = new QtCharts::QChartView(chart);
  chartView->setRenderHint(QPainter::Antialiasing);
  QMainWindow window;
  window.setCentralWidget(chartView);
  window.resize(400, 300);

  for (int i = 1; i <= totalExams; i++)
  {
    std::cout << "Enter score of exam " << i << " ";
    std::string userInput;
    getline(std::cin, userInput);
    int score = std::stoi(userInput);
    examScores.push_back(score);

    series->append(i, score);
    chart->addSeries(series);
    window.show();
    app.exec();
    chart->removeSeries(series);
  }

  return 0;
}

Now, there are many issues here from a software engineering perspective (e.g. not sanitizing input, and so on), but what I can't wrap my head around is how to rewrite app.exec();. I know it shouldn't be there, since it's blocking, and I know Qt isn't meant to be written this way.

So I'm wondering: how would you rewrite this example in order to make app.exec(); non-blocking and allow the program to receive user input on the terminal at all times?

Melvin Roest
  • 1,392
  • 1
  • 15
  • 31
  • Why not just move the stream input (`std::getline` etc.) to a separate thread and communicate with the main/GUI thread using [queued signals/slots](https://doc.qt.io/qt-5/signalsandslots.html)? – G.M. Jan 07 '21 at 09:40
  • Thanks :) I wonder how to do that though as I'm quite new with C++ (and haven't done threads in a while, I followed Maurice Herlihy's course a few years ago), once I figured a few solutions out, I'll post them as answers. – Melvin Roest Jan 07 '21 at 10:13

1 Answers1

0

This is how I put it into its own thread while still being able to handle CLI input. A lot of things are still wrong with this code from a code review perspective, but the basic functionality (putting it into a different thread) works.

#include <iostream>
#include <vector>
#include <thread>

#include <QApplication>
#include <QMainWindow>
#include <QChartView>
#include <QLineSeries>

std::vector<int> examScores;

void pollForUserInput(QtCharts::QLineSeries *series, int totalExams)
{
  for (int i = 1; i <= totalExams; i++)
  {
    std::cout << "\nEnter score of exam " << i << " " << std::endl;
    std::string userInput;
    getline(std::cin, userInput);
    int score = std::stoi(userInput);
    examScores.push_back(score);
    series->append(i, score);
  }

  std::cout << "done" << std::endl;
}

int main(int argc, char **argv)
{
  QApplication app(argc, argv);

  QtCharts::QLineSeries *series = new QtCharts::QLineSeries();
  int totalExams = 5;
  std::thread t1(pollForUserInput, series, totalExams);

  QtCharts::QChart *chart = new QtCharts::QChart();
  chart->legend()->hide();
  chart->addSeries(series);
  chart->createDefaultAxes();
  chart->axisX()->setRange(1, totalExams); // see https://stackoverflow.com/questions/38804179/how-to-repaint-a-qchart
  chart->axisY()->setRange(1, 10);         // see https://stackoverflow.com/questions/38804179/how-to-repaint-a-qchart
  QtCharts::QChartView *chartView = new QtCharts::QChartView(chart);
  chartView->setRenderHint(QPainter::Antialiasing);
  QMainWindow window;
  window.setCentralWidget(chartView);
  window.resize(400, 300);
  window.show();
  return app.exec();
}
Melvin Roest
  • 1,392
  • 1
  • 15
  • 31
  • I don't see the point of `QtCharts::QLineSeries** series` instead of `QtCharts::QLineSeries* series` since you always do `(*series)->append` where it could just be `series->append` – Vivick Jan 07 '21 at 12:44
  • I want to pass by reference and not copy it. I might be wrong that this is how it should be done. I’ll debug it. – Melvin Roest Jan 07 '21 at 14:21
  • It's a pointer. Copying a pointer or a pointer to a pointer is the same exact cost – Vivick Jan 07 '21 at 14:31
  • Ah, I was conflating 2 concepts, thanks. I changed it. – Melvin Roest Jan 07 '21 at 14:36
  • You're potentially accessing the `QtCharts::QLineSeries` instance from multiple threads simultaneously without any synchronization -- that's undefined behaviour. Please use [queued signals/slots](https://doc.qt.io/qt-5/signalsandslots.html) instead. – G.M. Jan 07 '21 at 21:34
  • So you're saying I should implement something similar to line 525 and 533 of the Qt source code itself, since it's not specified in the documentation (thus rendering it undefined behavior)? Source code link: https://code.qt.io/cgit/qt/qtcharts.git/tree/src/charts/xychart/qxyseries.cpp?h=dev – Melvin Roest Jan 09 '21 at 13:30