QProgressDialog is stopping earlier than expected

61 views Asked by At
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QFileDialog, QProgressDialog, QWidget
from PyQt5.QtCore import pyqtSignal, QObject, QThread
from openpyxl import load_workbook, Workbook


class ExcelMerger(QObject):
    progressChanged = pyqtSignal(int)
    mergeFinished = pyqtSignal()

    def merge_excel(self, files, progress_callback):
        wb_combined = Workbook()
        ws_combined = wb_combined.active

        for index, filename in enumerate(files, 1):
            progress = int((index / len(files)) * 100)
            progress_callback.emit(progress)

            wb = load_workbook(filename, read_only=False)
            ws = wb.active
            for row in ws.iter_rows(values_only=True):
                ws_combined.append(row)

        wb_combined.save("merged_file.xlsx")

        self.mergeFinished.emit()


class MergeThread(QThread):
    def __init__(self, files, excel_merger, progress_callback):
        super().__init__()
        self.files = files
        self.excel_merger = excel_merger
        self.progress_callback = progress_callback

    def run(self):
        self.excel_merger.merge_excel(self.files, self.progress_callback)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.excel_merger = ExcelMerger()
        self.excel_merger.progressChanged.connect(self.on_progress_changed)
        self.excel_merger.mergeFinished.connect(self.on_merge_finished)

    def initUI(self):
        self.setWindowTitle('Excel Merger')
        self.setGeometry(100, 100, 300, 200)

        layout = QVBoxLayout()

        self.merge_btn = QPushButton('Merge Excel Files', self)
        self.merge_btn.clicked.connect(self.merge_files)
        layout.addWidget(self.merge_btn)

        self.widget = QWidget()
        self.widget.setLayout(layout)
        self.setCentralWidget(self.widget)

    def merge_files(self):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        files, _ = QFileDialog.getOpenFileNames(self, "Select Excel files to merge", "", "Excel Files (*.xlsx *.xls)", options=options)

        if files:
            self.progress = QProgressDialog(self)
            self.progress.setLabelText("Merging files...")
            self.progress.setCancelButton(None)
            self.progress.setRange(0, 100)
            self.progress.show()

            self.thread = MergeThread(files, self.excel_merger,           self.excel_merger.progressChanged)
            self.thread.start()

    def on_progress_changed(self, value):
        self.progress.setValue(value)

    def on_merge_finished(self):
        self.progress.close()
        print("Files merged successfully.")


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

I tried to separate the merging thread and set up the main window, and then run the application's event-loop. But the progress bar is stopping before the file merging is fully complete.

In this PyQt5 code, I am trying to merge multiple excel files. The files are merging properly, but the progress bar that is displaying the percentage of files uploaded is closing before all the files are getting merged. I don't understand why this is happening.

1

There are 1 answers

4
ekhumoro On BEST ANSWER

The reason why the progress-dialog appears to stop early is that, by default, it will immediately auto-reset once it reaches the maximum, and this will automatically close the dialog as well. So the simplest solution to your problem is to make the following change:

self.progress.setAutoReset(False)

However, your current code is also a lot more convoluted than it needs to be, and needlessly creates a new dialog and a new thread every time a merge is started. I have therefore provided a simplified version of your example below. Hopefully the changes I have made are self-explanatory:

import sys
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QPushButton, QVBoxLayout,
    QFileDialog, QProgressDialog, QWidget, QMessageBox,
    )
from PyQt5.QtCore import pyqtSignal, QObject, QThread
from openpyxl import load_workbook, Workbook

class ExcelMerger(QObject):
    progressChanged = pyqtSignal(int)
    mergeFinished = pyqtSignal()

    def set_files(self, files):
        self.files = files

    def merge_excel(self):
        wb_combined = Workbook()
        ws_combined = wb_combined.active
        for index, filename in enumerate(self.files, 1):
            wb = load_workbook(filename, read_only=False)
            ws = wb.active
            for row in ws.iter_rows(values_only=True):
                ws_combined.append(row)
            progress = int((index / len(self.files)) * 100)
            self.progressChanged.emit(progress)
        QThread.sleep(1)
        wb_combined.save("merged_file.xlsx")
        self.mergeFinished.emit()

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.progress = QProgressDialog(self)
        self.progress.setLabelText("Merging files...")
        self.progress.setCancelButton(None)
        self.progress.setRange(0, 100)
        self.progress.setAutoReset(False)
        self.progress.cancel()
        self.thread = QThread(self)
        self.excel_merger = ExcelMerger()
        self.excel_merger.moveToThread(self.thread)
        self.thread.started.connect(self.excel_merger.merge_excel)
        self.excel_merger.progressChanged.connect(self.on_progress_changed)
        self.excel_merger.mergeFinished.connect(self.on_merge_finished)

    def initUI(self):
        self.setWindowTitle('Excel Merger')
        self.setGeometry(900, 100, 300, 200)
        layout = QVBoxLayout()
        self.merge_btn = QPushButton('Merge Excel Files', self)
        self.merge_btn.clicked.connect(self.merge_files)
        layout.addWidget(self.merge_btn)
        self.widget = QWidget()
        self.widget.setLayout(layout)
        self.setCentralWidget(self.widget)

    def merge_files(self):
        if not self.thread.isRunning():
            files = QFileDialog.getOpenFileNames(
                self, "Select Excel files to merge", "",
                "Excel Files (*.* *.xlsx *.xls)",
                options=QFileDialog.DontUseNativeDialog)[0]
            if files:
                self.excel_merger.set_files(files)
                self.progress.show()
                self.progress.activateWindow()
                self.thread.start()

    def on_progress_changed(self, value):
        self.progress.setValue(value)

    def on_merge_finished(self):
        self.thread.quit()
        self.progress.close()
        print("Files merged successfully.")

    def closeEvent(self, event):
        self.thread.quit()
        self.thread.wait()

if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = MainWindow()
    ex.show()
    sys.exit(app.exec_())