Progress Bar Does not Render Until Job is Complete

风格不统一 提交于 2020-06-28 07:43:46

问题


I am trying to make a progress bar for copying large files. However, currently the Dialog window goes black until the process is finished. I now understand I probably have to learn how to use treads and pass the data back into the GUI. But I still don't understand why the window fails to render completely. I'd understand if the window was unresponsive because the moveFilesWithProgress function is running. But within that function I am updating the progress bar value. I even tried adding QtGui.QGuiApplication.processEvents() in the hopes that it will update the gui before continuing to iterate.

I would appreciate any help to possibly fix this without a multi threaded process but if it is not possible I would greatly appreciate a dumbed down example of how to use Qthread. I found a related question but there was to much going on for me to understand the example. here is the link

example code:

#!/usr/bin/env python
import os
import sys
import shutil
from PyQt5.QtWidgets import (QApplication, QDialog, QLineEdit, QMainWindow,
                            QPushButton, QProgressBar)
import PyQt5.QtGui as QtGui

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.button = QPushButton("Copy", self)
        self.button.clicked.connect(self.archiveEntry)
        self.show()

    def archiveEntry(self):
        self.p = ProgressBar()
        self.p.show()
        Dir = "/media/zachlab/Windows/LinuxStorage/old/embryos"
        outDir = "/media/zachlab/Windows/LinuxStorage/old/out"
        run = self.p.moveFilesWithProgress(Dir, outDir)
        # if run:
        #    self.p.close


class ProgressBar(QDialog):
    def __init__(self):
        super().__init__()
        self.pbar = QProgressBar(self)

    def timerEvent(self, e):
        if self.step >= 100:
            self.timer.stop()
            self.btn.setText('Finished')
            return
        self.pbar.setValue(self.step)

    def update(self, value):
        self.pbar.setValue(value)
        QtGui.QGuiApplication.processEvents()

    def calculateAndUpdate(self, done, total):
        progress = int(round((done / float(total)) * 100))
        self.update(progress)
        

    def countFiles(self, directory):
        files = []
        if os.path.isdir(directory):
            for path, dirs, filenames in os.walk(directory):
                files.extend(filenames)
        return len(files)

    def makedirs(self, dest):
        if not os.path.exists(dest):
            os.makedirs(dest)

    def moveFilesWithProgress(self, src, dest):
        numFiles = self.countFiles(src)
        if os.path.exists(dest):
            return 0
        if numFiles > 0:
            self.makedirs(dest)
            numCopied = 0

            for path, dirs, filenames in os.walk(src):
                for directory in dirs:
                    destDir = path.replace(src, dest)
                    self.makedirs(os.path.join(destDir, directory))

                for sfile in filenames:
                    srcFile = os.path.join(path, sfile)
                    destFile = os.path.join(path.replace(src, dest), sfile)
                    shutil.copy(srcFile, destFile)
                    numCopied += 1
                    self.calculateAndUpdate(numCopied, numFiles)
                    self.show()
            return 1
        else:
            return 0


if __name__ == "__main__":
    app = QApplication(sys.argv)
    ex = MainWindow()
    sys.exit(app.exec_())

回答1:


Heavy tasks should not run on the main thread, you should use a thread. In this case I have replaced your logic, so there is a class that is in charge of copying the files informing through signs of progress, error, etc. An object of that class will live in another thread notifying the GUI through signals, in this case I will use QProgressDialog to show the progress.

#!/usr/bin/env python
import os
import sys
import shutil
import threading
from PyQt5 import QtCore, QtGui, QtWidgets

class MainWindow(QtWidgets.QMainWindow):
    startMoveFilesSignal = QtCore.pyqtSignal(str, str)

    def __init__(self):
        super(MainWindow, self).__init__()
        srcdir = "/media/zachlab/Windows/LinuxStorage/old/embryos"
        dstdir = "/media/zachlab/Windows/LinuxStorage/old/out"
        self.le_src = QtWidgets.QLineEdit(srcdir)
        self.le_dst = QtWidgets.QLineEdit(dstdir)
        self.button = QtWidgets.QPushButton("Copy")
        self.button.clicked.connect(self.archiveEntry)

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)
        lay = QtWidgets.QFormLayout(central_widget)
        lay.addRow("From: ", self.le_src)
        lay.addRow("To: ", self.le_dst)
        lay.addRow(self.button)

        self.progressbar = QtWidgets.QProgressDialog(self)
        self.progressbar.hide()

        thread = QtCore.QThread(self)
        thread.start()
        self.helper = MoveFileHelper()
        self.startMoveFilesSignal.connect(self.helper.moveFilesWithProgress)
        self.helper.progressChanged.connect(self.progressbar.setValue)
        self.helper.finished.connect(self.on_finished)
        self.helper.started.connect(self.progressbar.show)
        self.helper.errorOccurred.connect(self.on_errorOcurred)
        self.helper.moveToThread(thread)

    @QtCore.pyqtSlot()
    def archiveEntry(self):
        self.startMoveFilesSignal.emit(self.le_src.text(), self.le_dst.text())
        self.progressbar.hide()

    @QtCore.pyqtSlot()
    def on_finished(self):
        self.button.setText('Finished')

    @QtCore.pyqtSlot(str)
    def on_errorOcurred(self, msg):
        QtWidgets.QMessageBox.critical(self, "Error Ocurred", msg)

class MoveFileHelper(QtCore.QObject):
    progressChanged = QtCore.pyqtSignal(int)
    started = QtCore.pyqtSignal()
    finished = QtCore.pyqtSignal()
    errorOccurred = QtCore.pyqtSignal(str)

    def calculateAndUpdate(self, done, total):
        progress = int(round((done / float(total)) * 100))
        self.progressChanged.emit(progress)

    @staticmethod
    def countFiles(directory):
        count = 0
        if os.path.isdir(directory):
            for path, dirs, filenames in os.walk(directory):
                count += len(filenames)
        return count

    @staticmethod
    def makedirs(dest):
        if not os.path.exists(dest):
            os.makedirs(dest)

    @QtCore.pyqtSlot(str, str)
    def moveFilesWithProgress(self, src, dest):
        numFiles = MoveFileHelper.countFiles(src)
        if os.path.exists(dest):
            self.errorOccurred.emit("Dest exist")
            return 
        if numFiles > 0:
            self.started.emit()
            MoveFileHelper.makedirs(dest)
            numCopied = 0
            for path, dirs, filenames in os.walk(src):
                for directory in dirs:
                    destDir = path.replace(src, dest)
                    MoveFileHelper.makedirs(os.path.join(destDir, directory))

                for sfile in filenames:
                    srcFile = os.path.join(path, sfile)
                    destFile = os.path.join(path.replace(src, dest), sfile)
                    shutil.copy(srcFile, destFile)
                    numCopied += 1
                    self.calculateAndUpdate(numCopied, numFiles)
            self.finished.emit()

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    ex = MainWindow()
    ex.resize(640, ex.sizeHint().height())
    ex.show()
    sys.exit(app.exec_())


来源:https://stackoverflow.com/questions/53751909/progress-bar-does-not-render-until-job-is-complete

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!