【问题标题】:QProgressDialog only shows after long-running code is finishedQProgressDialog 仅在长时间运行的代码完成后显示
【发布时间】:2017-11-07 10:57:40
【问题描述】:

我有一个根据 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. 调用showLoader()方法时如何显示QProgressDialog?
  2. 我是否必须在不同的线程中执行我的计算和文件解析,如果必须我该怎么做?
  3. 如果我想在QProgressDialog 中显示更多信息,例如更新文本和进度,我该怎么做?

其他问题

@ekhumoro 指出的解决方案效果很好。我看到加载器和文件被正确解析。我现在的问题是更新我现有的MainWindow 不起作用。

执行代码后,我看到一个小窗口弹出,但它立即消失了。 (我遇到了这样的问题,它与 Qt 背景中的 C++ 垃圾收集器有关。但据我了解,布局应该保留对 ParsedDataWidget 的引用,所以这对我来说没有意义。)还有 @ 987654337@ 是一个小部件,应该添加到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 方法起作用?

编辑

我正在尝试对代码进行一些更改。如果我用QLabel 替换ParsedDataWidget,我会得到以下结果:

如果我关闭窗口 python 崩溃。

解决方案

通过进一步研究,我发现了我的问题:你不应该在 PyQt 中使用线程,你应该改用 SIGNALS (written here)

所以我更改了 worker 的代码,我添加了另一个名为 finishedParsing 的 SIGNAL,如果加载完成,则会发出该代码。这个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)

现在可以了!

【问题讨论】:

  • 是的,你绝不能尝试在主线程之外执行 gui 操作。跨线程信号保证是线程安全的,所以定义自定义信号与主线程通信,并且只更新与其连接的槽中的 gui。

标签: python multithreading qt pyqt5 qprogressdialog


【解决方案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_())

【讨论】:

  • 非常感谢,这对我帮助很大。文件读取进度的更新也可以正常工作。加载完成后我仍然有问题。为了更好地标记,我在原始问题中将其编写为编辑。
猜你喜欢
  • 1970-01-01
  • 2016-06-20
  • 2016-07-30
  • 2010-11-21
  • 1970-01-01
  • 2014-01-22
  • 1970-01-01
  • 2015-12-10
  • 2018-02-20
相关资源
最近更新 更多