0

I tried to create QProgressBar according to Manual. Yet it works really bad (for instance, if I create QProgressDialog in the constructor, it will appear as soon as app is running, so I decided to use QProgressBar). But there is a problem:

enter image description here

Although I used advices from the internet. My code:

UPD![2]

// StudentAbsenceTableApp.h
using Job = std::function<void ()>;
Q_DECLARE_METATYPE(Job)
class StudentAbsenceTableApp{
public:
    StudentAbsenceTableApp(QWidget *parent = 0);

private:
    Q_SIGNAL void reqLoadFile(const QString& fileName);
    Q_SIGNAL void reqSaveFile(const QString& fileName);
    Q_SIGNAL void reqGui(const Job&);

    bool documentModified;
    QProgressBar *progressBar;
};

// StudentAbsenceTableApp.cpp
StudentAbsenceTableApp::StudentAbsenceTableApp(QWidget *parent)
   : QMainWindow(parent)
{
    // ...

    setStatusBar(new QStatusBar(this));

    qRegisterMetaType<Job>();
    progressBar = new QProgressBar(statusBar());
    progressBar->setMinimum(0);
    progressBar->setMaximum(0);
    progressBar->setMaximumWidth(150);
    progressBar->hide();
    statusBar()->addPermanentWidget(progressBar);

    connect(this, &StudentAbsenceTableApp::reqLoadFile, this, [this] (const QString& fileName){
        QtConcurrent::run(this, &StudentAbsenceTableApp::loadFile, fileName);
    });
    connect(this, &StudentAbsenceTableApp::reqGui, [this](const Job & job){
        job();
    });
}

// funtion that emit reqLoadFile(fileName)

bool StudentAbsenceTableApp::loadFile(const QString& fileName)
{
    reqGui([=] () { progressBar->show(); });
    auto xmlParser = XMLParser(model);

    try
    {
        reqGui([&] () {
            xmlParser.read(fileName);
            setCurrentFileName(fileName);
            statusBar()->showMessage(tr("Файл загружен"), 2000);
            documentModified = false;
        });
    }
    catch(FileOpenException)
    {
        reqGui([=] () {
            QMessageBox::warning(this, "Ошибка!", "Ошибка открытия файла!", QMessageBox::Ok);
            statusBar()->showMessage(tr("Загрузка отменена"), 2000);
        });
        return false;
    }
    catch(FileReadException)
    {
        reqGui([=] () {
            QMessageBox::warning(this, "Ошибка!", "Ошибка чтения файла!", QMessageBox::Ok);
            statusBar()->showMessage(tr("Загрузка отменена"), 2000);
        });
        return false;
    }

    reqGui([=] () { progressBar->hide(); });
    return true;
}

I don't know how to write code, that is possible to compile, because there is a lot of code.

  • Just make a signal that takes a `QString` and an `int` as arguments and connect it to `QStatusBar::showMessage`. Then emit this signal in `experimentFunction`. Forget about that lambda. Go back to your original version. And you can use an automatic variable for `XMLParser`, I don't see any reason why you would initialize it with `new` in this case. – thuga Jun 09 '17 at 06:25
  • @thuga I can't understand how `QStatusBar::showMessage` can show `QProgressBar`. – Марк Логвинович Jun 09 '17 at 09:39
  • 1
    Nobody ever said it could. I'm just pointing out an error in your code. You were accessing `QStatusBar` widget from a different thread. – thuga Jun 09 '17 at 10:09
  • Those messages don't originate in the code from the answer. – Kuba hasn't forgotten Monica Jun 10 '17 at 19:41

1 Answers1

5

No QWidget (and derived classes) methods provided by Qt are thread-safe. Thus you can't access QProgressBar nor any other widgets from any thread other then the GUI thread.

The experimentFunction runs in a non-GUI thread and thus must not access widgets. You must figure out some other means of communication, e.g. using signals and slots. Recall that you're free to emit signals in experimentFunction, since the signal implementations are by contract thread-safe.

It's all really simple, and you don't need the future watcher. In your attempts to "fix" the issue, you've hopelessly combobulated your code.

For other ways of invoking methods safely across threads, see this question and that question.

// https://github.com/KubaO/stackoverflown/tree/master/questions/thread-progress-future-44445248
#include <QtConcurrent>
#include <QtWidgets>
#include <exception>
#include <functional>

struct FileOpenException : std::exception {};
struct FileReadException : std::exception {};
struct Model {};
struct XMLParser {
   XMLParser(Model &) {}
   void read(const QString &) {
      static int outcome;
      QThread::sleep(3);
      switch (outcome++ % 3) {
      case 0: return;
      case 1: throw FileOpenException();
      case 2: throw FileReadException();
      }
   }
};

using Job = std::function<void()>;
Q_DECLARE_METATYPE(Job)

class StudentAbsenceTable : public QMainWindow {
   Q_OBJECT
   QStatusBar m_statusBar;
   QProgressBar m_progress;
   QPushButton m_start{"Start Concurrent Task"};
   Model m_model;
   bool m_documentModified = {};
public:
   StudentAbsenceTable() {
      qRegisterMetaType<Job>();
      m_statusBar.addPermanentWidget(&m_progress);
      m_progress.setMinimum(0);
      m_progress.setMaximum(0);
      m_progress.setMaximumWidth(150);
      m_progress.hide();
      setStatusBar(&m_statusBar);
      setCentralWidget(&m_start);
      connect(&m_start, &QPushButton::clicked, this, [this]{
         m_start.setEnabled(false);
         QtConcurrent::run(this, &StudentAbsenceTable::loadFile);
      });
      connect(this, &StudentAbsenceTable::reqGui, this, [this](const Job & job){
         job();
      });
   }
private:
   bool loadFile() {
      reqGui([=]{ m_progress.show(); });
      auto fileName = QStringLiteral("/media/bsuir/data.xml");
      auto xmlParser = XMLParser(m_model);
      try {
         xmlParser.read(fileName);
         reqGui([=]{
            setCurrentFileName(fileName);
            statusBar()->showMessage(tr("Файл загружен"), 2000);
            m_documentModified = false;
         });
      }
      catch(FileOpenException&) {
         reqGui([=]{
            QMessageBox::warning(this, "Ошибка!", "Ошибка открытия файла!", QMessageBox::Ok);
            statusBar()->showMessage(tr("Загрузка отменена"), 2000);
         });
      }
      catch(FileReadException&) {
         reqGui([=]{
            QMessageBox::warning(this, "Ошибка!", "Ошибка чтения файла!", QMessageBox::Ok);
            statusBar()->showMessage(tr("Загрузка отменена"), 2000);
         });
      }
      reqGui([=]{ m_progress.hide(); m_start.setEnabled(true); });
      return false;
   }
   Q_SIGNAL void reqGui(const Job &);
   void setCurrentFileName(const QString &) {}
};

int main(int argc, char ** argv) {
   QApplication app(argc, argv);
   StudentAbsenceTable ui;
   ui.setMinimumSize(350, 350);
   ui.show();
   return app.exec();
}
#include "main.moc"
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313