2

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_())
Community
  • 1
  • 1
Stephen
  • 56
  • 7

1 Answers1

3

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_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • One quick thing, how would you prevent the QProgress Dialog window from popping up at the beginning aside from moving the building and connecting bits into the archiveEntry function. Other than that this is exactly what I need thank you! – Stephen Dec 13 '18 at 01:00
  • @Stephen You must use hide as I already update my answer, on the other hand I did not understand the rest. – eyllanesc Dec 13 '18 at 01:05
  • please add self.progressbar.setCancelButton(None) in class MainWindow(QtWidgets.QMainWindow): or implement QProgressDialog Cancel Button functionality, can-t do it myself suggestuon queque is full – pippo1980 Mar 07 '22 at 20:56