Предотвратить блокировку цикла событий в модальном диалоговом окне

Я разрабатываю приложение, в котором пользователь может анализировать некоторые двоичные файлы. После того, как он нажмет кнопку «анализ», он может сначала выбрать некоторые файлы, которые затем будут проанализированы. Пока приложение обрабатывает файлы, я хотел бы отобразить модальный диалог, который информирует пользователя о ходе выполнения (панель QProgressBar) и уже проанализированных файлах (список QListView/listModel).

Мой текущий подход состоит в том, чтобы переопределить метод exec() подсистемы QDialog. Таким образом, я мог бы просто позвонить

parseAssistant.exec()

Текущая реализация выглядит так:

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;
}

После запуска этого (блокирующего) метода пользователь либо проанализировал все файлы, либо где-то отменил прогресс. Чтобы достичь этого, я попытался использовать QApplication::processEvents в цикле while (который кажется немного запаздывающим, поскольку он прогрессирует только после завершения синтаксического анализа файла) или передать тяжелые вычисления какой-либо реализации QConcurrent (::run, ::отображено). К сожалению, я не знаю, как вернуть поток программы обратно в метод exec() после завершения QFuture, не полагаясь на цикл с интенсивным использованием ЦП, например:

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

Есть ли более разумный подход к модальному диалогу, который выполняет тяжелые вычисления (которые могут быть отменены пользователем) без блокировки цикла обработки событий?


person Jonas Möller    schedule 15.03.2018    source источник


Ответы (2)


Во-первых, я бы не стал подклассом Qdialog, а просто использовал бы QFutureWatcher и подключил сигнал наблюдателя finished к слоту диалога close следующим образом:

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

Функция синтаксического анализа может быть методом класса, производного от QObject, например:

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);
};

Вы можете подключить сигнал fileParsed к выбранному слоту и оттуда соответствующим образом установить значение индикатора выполнения.

person p-a-o-l-o    schedule 15.03.2018
comment
Как передать функцию Parser::parse в QtConcurrent::run, сохраняя при этом файлы и анализатор (для parser.parse)? - person Jonas Möller; 15.03.2018
comment
Наличие объекта Parser parser: QtConcurrent::run(parser, &Parser::parse); - person p-a-o-l-o; 15.03.2018

Мой личный подход был бы таким:

  • создайте отдельный поток и выполняйте там обработку (QThread; std::thread тоже надо потренироваться)
  • предоставить сигнал, который информирует о файле, который в данный момент обрабатывается
  • возможно другой сигнал, информирующий о прогрессе в %
  • другой сигнал информирует о завершении обработки, выдаваемый непосредственно перед завершением потока.
  • предоставьте вашему диалогу соответствующие слоты и подключите их к сигналам (поскольку задействованы разные потоки, убедитесь, что тип соединения Qt::QueuedConnection)
person Aconcagua    schedule 15.03.2018