Jonas Möller
Jonas Möller

Reputation: 363

Prevent event loop blocking in modal dialog

I'm developing an application where a user may parse some binary files. Once he clicks the "parse"-button, he first may select some files, which are parsed afterwards. While the application is processing the files, I'd like to display a modal dialog, which informs the user about the progress (QProgressBar bar) and the already parsed files (QListView list / listModel).

My current approach is to override the exec()-method of a QDialog-sublcass. This way I could just call

parseAssistant.exec()

The current implementation looks like this:

class ParseAssistant : public QDialog { public: int exec(); };

int ParseAssistant::exec()
{
    bar->setMaximum(files.size());

    this->show();
    this->setModal(true);

    for (int i = 0; i < files.size(); i++) {
        PluginTable* table = parser.parse(files[i]);

        // do something with the table
        // saveTableintoDB();

        // update GUI
        // bar->setValue(i);
        // listModel->insertRow(0, new QStandardItem(files[i]));
    }
    this->hide();

    return QDialog::Accepted;
}

After this (blocking) method has run, the user either has parsed all files or canceled the progress somewhere. To achieve this I attempted to use QApplication::processEvents in the while-loop (which feels kinda laggy as it's only progressed when a file has finished parsing) or to outsource the heavy calculation(s) to some QConcurrent implementation (::run, ::mapped). Unfortunately, I don't know how to return the program flow back to the exec() method once the QFuture has finished without relying on some CPU-intense loop like:

while (!future.isFinished()) { QApplication::processEvents(); }

Is there a smarter approach to having a modal dialog, which runs a heavy calculation (which may be canceled by the user) without blocking the eventloop?

Upvotes: 0

Views: 856

Answers (2)

p-a-o-l-o
p-a-o-l-o

Reputation: 10047

I wouldn't subclass Qdialog, in the first place, but just use a QFutureWatcher and connect the watcher finished signal to the dialog close slot, this way:

QDialog d;

QFutureWatcher<void> watcher;
QObject::connect(&watcher, &QFutureWatcher<void>::finished, &d, &QDialog::close);

QFuture<void> future = QtConcurrent::run(your_parse_function);
watcher.setFuture(future);

d.exec();

//control returns here when your_parse_function exits

The parse function could be a method in a QObject derived class, like this:

class Parser : public QObject
{
  Q_OBJECT
public:
    void parse()
    {
        for (int i = 0; i < files.size(); i++) {

            PluginTable* table = parser.parse(files[i]);

            emit fileParsed(i, files.size);

            // ...
        }
    }    
signals:
    void fileParsed(int id, int count);
};

You can connect the fileParsed signal to a slot of choice, and from there set the progress bar value accordingly.

Upvotes: 2

Aconcagua
Aconcagua

Reputation: 25516

My personal approach would be:

  • create a separate thread and do the processing there (QThread; std::thread should do the trick as well)
  • provide a signal that informs about the file currently being processed
  • possibly another signal informing about progress in %
  • another signal informs that processing is done, emitted just before the thread ends
  • provide your dialog with appropriate slots and connect them to the signals (as different threads involved, make sure connection type is Qt::QueuedConnection)

Upvotes: 0

Related Questions