QProgressDialog показывает только после того, как закончил долго работающий код

У меня есть программа, разработанная в соответствии с моделью MVC. На мой взгляд, есть open кнопка, где я могу открыть файлы. Файлы анализируются, и с содержимым файла выполняется большой расчет.

Теперь я хочу отобразить загрузчик, чтобы указать, что пользователь должен ждать. Я совершенно новичок в Python и Qt. Я использую PyQt5 (Qt 5.6.2) с Python 3.6.2.

Я добавил showLoader() метод к openFiles() метод моего взгляда:

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, controller, parent = None):
        # initializing the window

    def showOpenDialog(self):
        files, filters = QtWidgets.QFileDialog.getOpenFileNames(self, 'Open file(s)', '',
                                          "Raw files (*.rw.dat);;Data files (*.dat)" + 
                                          ";;Text files (*.txt);;All files (*)")

        self.showLoader("Loading file(s)")

        self.doSomeStuffWithTheFiles(files)

        self.hideLoader()

    def showLoader(self, text):
        self._progress = QtWidgets.QProgressDialog(text, "Abort", 0, 0, self);
        self._progress.setWindowModality(QtCore.Qt.WindowModal);

Появится загрузчик, но он появится после загрузки файла. Даже не сразу после загрузки файла, но это займет дополнительные 1-2 секунды после того, как все будет сделано (включая некоторые перерисовки окна)

Я много читал о потоках, поэтому я предполагаю, что анализ файла блокирует загрузчик прогресса, что имеет смысл. Я прочитал, что я должен добавить QProgressDialog в слот (я действительно не знаю, что это такое), но это не помогает мне, потому что я хочу QProgressDialog отображаться после QFileDialog,

Я также читал кое-что о добавлении QtWidgets.QApplication.processEvents() перекрасить окно, но это не сработало для меня (или я использовал его неправильно).

Итак, мои вопросы:

  1. Как мне отобразить QProgressDialog когда я звоню showLoader() метод?
  2. Должен ли я выполнять свои вычисления и анализ файла в другом потоке, и если нужно, как мне это сделать?
  3. Если бы я хотел показать больше информации в QProgressDialog Как обновить текст и прогресс, как мне это сделать?

Дальнейший вопрос

Решение, указанное @ekhumoro, работает отлично. Я вижу загрузчик и файлы анализируются правильно. Моя проблема сейчас в том, что обновление моего существующего MainWindow не работает.

После выполнения кода я вижу, что всплывает небольшое окно, но оно мгновенно исчезает. (У меня была такая проблема, и речь шла о сборщике мусора в C++ на фоне Qt. Но в моем понимании компоновка должна сохранять ссылку на ParsedDataWidget так что это не имеет смысла для меня.) Также ParsedDataWidget это виджет, который должен быть добавлен в layout "встроенный" и не отображается как "окно".

# a class that handles the data parsing of each file and creates an
# object that contains all the data with some methods...
class DataParser
    def __init__(self, data):
        # handle the data

# displaying the parsed data in a fancy way
class ParsedDataWidget(QtWidgets.QWidget)
    def __init__(self, data):
        # create some UI

# the worker class just like @ekhumoro wrote it (stripped down to
# relevant code)
class Worker(QtCore.QObject):
    def run(self):
        self._stop = False
        for count, file in enumerate(self._files, 1):

            # parse the data in the DataParser and create an object
            # of the files data
            data = DataParser(file)

            # works fine, the data is parsed correctly
            print(data)

            # does not work
            window.addParsedData(data)

            self.loaded.emit(count, file)
            if self._stop:
                break
        self.finished.emit()

# the window class like mentioned before (or like @ekhumoro wrote it)
class Window(QtWidgets.QWidget):
    def __init__(self):
        self._data_container = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout()
        self._data_container.setLayout(layout)

    def addParsedData(data):
        data_widget = ParsedDataWidget(data)

        layout = self._data_container.layout()
        layout.addWidget(data_widget)

Так что мне нужно сделать, чтобы получить addParsedData способ работы?

редактировать

Я пытался некоторые изменения кода. Если я заменю ParsedDataWidget с QLabel Я получаю следующий результат:

Если я закрою окно, Python падает.

Решение

Проведя дальнейшее исследование, я обнаружил, что моя проблема: вы не должны использовать потоки с PyQt, вы должны использовать SIGNALS вместо ( написано здесь)

Поэтому я изменил код работника, я добавил другой SIGNAL называется finishedParsing который испускается, если загрузка завершена. это SIGNAL держит DataParser, Может выглядеть так:

class Worker(QtCore.QObject):
    finishedParsing = QtCore.pyqtSignal(DataParser)

    def run(self):
        self._stop = False
        for count, file in enumerate(self._files, 1):

            # parse the data in the DataParser and create an object
            # of the files data
            data = DataParser(file)

            # emit a signal to let the window know that this data is
            # ready to use
            self.finishedParsing.emit(data)

            self.loaded.emit(count, file)
            if self._stop:
                break
        self.finished.emit()

class Window(QtWidgets.QWidget):
    def showOpenDialog(self):
        if files and not self.thread.isRunning():
            # do the opening stuff like written before
            self.worker = Worker(files)

            #...

            self.worker.finishedParsing.connect(self.addParsedData)

Это работает сейчас!

1 ответ

Решение

Ниже приведен пример, который реализует то, что вы просили. В реальном использовании QThread.sleep строка должна быть заменена вызовом функции, которая обрабатывает каждый файл. Это можно определить как метод Worker класс, или передал в качестве аргумента его __init__,

import sys, os
from PyQt5 import QtCore, QtWidgets

class Worker(QtCore.QObject):
    loaded = QtCore.pyqtSignal(int, str)
    finished = QtCore.pyqtSignal()

    def __init__(self, files):
        super().__init__()
        self._files = files

    def run(self):
        self._stop = False
        for count, file in enumerate(self._files, 1):
            QtCore.QThread.sleep(2) # process file...
            self.loaded.emit(count, file)
            if self._stop:
                break
        self.finished.emit()

    def stop(self):
        self._stop = True

class Window(QtWidgets.QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.button = QtWidgets.QPushButton('Choose Files')
        self.button.clicked.connect(self.showOpenDialog)
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.button)
        self.thread = QtCore.QThread()

    def showOpenDialog(self):
        files, filters = QtWidgets.QFileDialog.getOpenFileNames(
            self, 'Open file(s)', '',
            'Raw files (*.rw.dat);;Data files (*.dat)'
            ';;Text files (*.txt);;All files (*)',
            'All files (*)')
        if files and not self.thread.isRunning():
            self.worker = Worker(files)
            self.worker.moveToThread(self.thread)
            self.worker.finished.connect(self.thread.quit)
            self.thread.started.connect(self.worker.run)
            self.thread.finished.connect(self.worker.deleteLater)
            self.showProgress(
                'Loading file(s)...', len(files), self.worker.stop)
            self.worker.loaded.connect(self.updateProgress)
            self.thread.start()

    def updateProgress(self, count, file):
        if not self.progress.wasCanceled():
            self.progress.setLabelText(
                'Loaded: %s' % os.path.basename(file))
            self.progress.setValue(count)
        else:
            QtWidgets.QMessageBox.warning(
                self, 'Load Files', 'Loading Aborted!')

    def showProgress(self, text, length, handler):
        self.progress = QtWidgets.QProgressDialog(
            text, "Abort", 0, length, self)
        self.progress.setWindowModality(QtCore.Qt.WindowModal)
        self.progress.canceled.connect(
            handler, type=QtCore.Qt.DirectConnection)
        self.progress.forceShow()

if __name__ == '__main__':

    app = QtWidgets.QApplication(sys.argv)
    window = Window()
    window.setGeometry(600, 100, 100, 50)
    window.show()
    sys.exit(app.exec_())
Другие вопросы по тегам