Python QProgressDialog仅在长时间运行的代码完成后显示

Python QProgressDialog仅在长时间运行的代码完成后显示,python,multithreading,qt,pyqt5,qprogressdialog,Python,Multithreading,Qt,Pyqt5,Qprogressdialog,我有一个根据MVC模式设计的程序。我的视图有一个open按钮,我可以在其中打开文件。对文件进行解析,并对文件内容进行大量计算 现在我想显示一个加载器来指示用户应该等待。我对Python和Qt完全陌生。我将PyQt5(Qt5.6.2)与Python3.6.2一起使用 我在视图的openFiles()方法中添加了showLoader()方法: class MainWindow(QtWidgets.QMainWindow): def __init__(self, controller, par

我有一个根据MVC模式设计的程序。我的视图有一个
open
按钮,我可以在其中打开文件。对文件进行解析,并对文件内容进行大量计算

现在我想显示一个加载器来指示用户应该等待。我对Python和Qt完全陌生。我将PyQt5(Qt5.6.2)与Python3.6.2一起使用

我在视图的
openFiles()
方法中添加了
showLoader()
方法:

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
之后显示

我还阅读了一些关于添加
qtwidkets.QApplication.processEvents()
来重新绘制窗口的内容,但这对我不起作用(或者我用错了)

因此,我的问题是:

  • 调用
    showLoader()
    方法时,如何显示
    QProgressDialog
  • 我必须在不同的线程中执行计算和文件解析吗?如果必须,我该如何执行
  • 如果我想在
    QProgressDialog
    中显示更多信息,比如更新文本和进度,我该怎么做
  • 进一步的问题

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

    在执行代码后,我看到一个小窗口弹出,但它会立即消失。(我遇到了这样的问题,它是关于QT后台的C++垃圾收集器。但是在我的理解中,布局应该保持对<代码> PARSEDATAIDWIGET < /C>的引用,所以这对我来说没有意义。”<代码> PARSEDATAWIDGET 是一个小部件,应该添加到<代码>布局< /代码>“内联”。而不是作为“窗口”出现

    那么,为了让
    addParsedData
    方法工作,我必须做些什么呢

    编辑

    我试着修改代码。如果我用
    QLabel
    替换
    ParsedDataWidget
    ,我会得到以下结果:

    如果我关闭窗口,python将崩溃

    解决方案

    通过进一步的研究,我发现了我的问题:不应该在PyQt中使用线程,而应该使用
    信号()

    因此,我更改了工人的代码,添加了另一个名为
    finishedParsing
    SIGNAL
    ,该信号在加载完成时发出。此
    信号
    保存
    数据解析器
    。可能是这样的:

    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)
    

    现在可以了

    下面的示例实现了您的要求。在实际使用中,
    QThread.sleep
    行应替换为处理每个文件的函数调用。这可以定义为
    Worker
    类的方法,也可以作为其
    \uuuu init\uuuu
    的参数传入

    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_())
    

    非常感谢,这对我帮助很大。此外,文件读取进度的更新工作正常。当装载完成时,我仍然有一个问题。为了更好的标记,我在最初的问题中把它作为一个编辑来写。是的,你绝对不能尝试在主线程之外执行gui操作。跨线程信号保证是线程安全的,因此定义与主线程通信的自定义信号,并且只更新与它们连接的插槽中的gui。
    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_())