Search code examples
pythonpyqtpyqt5qprogressbar

Add a progress Bar to a method in PyQt5


I have a simple PyQt5 app here to convert from Pdf to excel. I wanted to add a progress bar to the method convert below when I click the button to convert as it takes sometime to execute,hence it will be great to have some visualization.

Here is my code below:

I am loading the ui from the Qtdesigner. Also I can add a progress bar somewhere that can be attached to the pushButton_3 . Progress bar can be called 'ProgressBar' . Please let me know how you can accomplish it as most of the answers here doesn't really answer this question.


import sys
from PyQt5.QtWidgets import QMainWindow,QApplication,QTableView, QWidget, QFileDialog,QPushButton,QVBoxLayout,QMessageBox
from PyQt5 import uic
from PyPDF2 import PdfFileReader
from PyQt5 import QtCore,QtGui
import pandas as pd
import tabula
import re
from PandasModel import PandasModel

class App(QMainWindow):

    def __init__(self):
        QWidget.__init__(self)
        uic.loadUi('designUI.ui',self)
        self.pushButton.clicked.connect(self.openFileNameDialog)
        self.pushButton_3.clicked.connect(self.convert)
        self.pushButton_2.clicked.connect(self.view)
        self.pushButton_4.clicked.connect(self.saveFileDialog)

    def openFileNameDialog(self):    
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        fileName, _ = QFileDialog.getOpenFileName(self,"Open File", "","All Files (*);;Python Files (*.py)")#, options=options)
        if fileName:
            self.file=fileName

    def view(self):
        model=PandasModel(self.converted_file)
        self.tableView.setModel(model)

    def convert(self):
        self.converted_file=self.pdf2excel(self.file)

    def saveFileDialog(self):    
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        fileName, _ = QFileDialog.getSaveFileName(self, 'Save File', '', ".xls(*.xls)")
        if fileName:
            self.converted_file.to_excel(fileName)
            msg=QMessageBox()
            msg.setText('File is Saved')
            msg.setStandardButtons(QMessageBox.Ok)
            #msg.buttonClicked.connect(msgbtn) 
            msg.exec_()

    def pdf2excel(self,pdf_file):    
        pdf = PdfFileReader(open(pdf_file,'rb'))
        length=pdf.getNumPages()        
        result=pd.DataFrame(columns=['Department','Employment No','Employment Name',"Hire Date","Term Date","Birth Date",
         "Seniority Date","Pay Code","FT/PT/S","Status"])        
        page=1
        while page <= length:
            df=tabula.read_pdf(pdf_file, pages = str(page),lattice =True, area=(75.775,16.0,572.715,779.29))[1:]
            pattern = re.compile(r'(\s){2,}')
            df=pd.DataFrame(df[df.columns[0]].replace(pattern,","))
            df=df['Unnamed: 0'].str.split(",",expand=True)
            df=df.rename(columns={0:'Department',
              1:'Employment No',2:'Employment Name',3:"Hire Date",4:"Term Date",5:"Birth Date",
              6:"Seniority Date",7:"Pay Code",8:"FT/PT/S",9:"Status"})
            result=result.append(df,ignore_index=True)
            page+=1
        result["Hire Date"]=pd.to_datetime(result["Hire Date"])
        result["Term Date"]=pd.to_datetime(result["Term Date"])
        result["Days Difference"]=(result['Term Date']-result['Hire Date']).dt.days
        result=result.dropna(how='all')
        result=result.drop(columns=['Birth Date','Pay Code','Status'])
        result=result[['Department','Employment No','Employment Name',"Hire Date","Term Date","Days Difference",
         "Seniority Date","FT/PT/S"]]
        return result

if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setWindowIcon(QtGui.QIcon('pdf-to-excel-icon.png'))
    ex = App()
    ex.show()
    sys.exit(app.exec_())  


Solution

  • First of all I recommend making verifications that the variables exist or have a certain size, for example when trying to use your code with any pdf your code is broken.

    Going to the problem, when you run pdf2excel you will see that you can not change the size of the window because the GUI is frozen, so it must run on another thread and send the information as the progress and the dataframe through signals. In this case I will create a worker(QObject) that will live in another thread that has the pdf2excel function.

    import sys
    from functools import partial
    from PyQt5 import QtCore, QtGui, QtWidgets, uic
    from PyPDF2 import PdfFileReader
    import pandas as pd
    import tabula
    import re
    from PandasModel import PandasModel
    
    
    class PdfObject(QtCore.QObject):
        progressChanged = QtCore.pyqtSignal(int)
        maximumChanged = QtCore.pyqtSignal(int)
        pandasChanged = QtCore.pyqtSignal(pd.DataFrame)
    
        @QtCore.pyqtSlot(str)
        def pdf2excel(self, pdf_file):
            pdf = PdfFileReader(open(pdf_file, "rb"))
            length = pdf.getNumPages()
            result = pd.DataFrame(
                columns=[
                    "Department",
                    "Employment No",
                    "Employment Name",
                    "Hire Date",
                    "Term Date",
                    "Birth Date",
                    "Seniority Date",
                    "Pay Code",
                    "FT/PT/S",
                    "Status",
                ]
            )
            self.maximumChanged.emit(length)
            page = 1
            while page <= length:
                self.progressChanged.emit(page)
                df = tabula.read_pdf(
                    pdf_file,
                    pages=str(page),
                    lattice=True,
                    area=(75.775, 16.0, 572.715, 779.29),
                )[1:]
                pattern = re.compile(r"(\s){2,}")
                df = pd.DataFrame(df[df.columns[0]].replace(pattern, ","))
                df = df["Unnamed: 0"].str.split(",", expand=True)
                df = df.rename(
                    columns={
                        0: "Department",
                        1: "Employment No",
                        2: "Employment Name",
                        3: "Hire Date",
                        4: "Term Date",
                        5: "Birth Date",
                        6: "Seniority Date",
                        7: "Pay Code",
                        8: "FT/PT/S",
                        9: "Status",
                    }
                )
                result = result.append(df, ignore_index=True)
                page += 1
            result["Hire Date"] = pd.to_datetime(result["Hire Date"])
            result["Term Date"] = pd.to_datetime(result["Term Date"])
            result["Days Difference"] = (
                result["Term Date"] - result["Hire Date"]
            ).dt.days
            result = result.dropna(how="all")
            result = result.drop(columns=["Birth Date", "Pay Code", "Status"])
            result = result[
                [
                    "Department",
                    "Employment No",
                    "Employment Name",
                    "Hire Date",
                    "Term Date",
                    "Days Difference",
                    "Seniority Date",
                    "FT/PT/S",
                ]
            ]
            self.pandasChanged.emit(result)
    
    
    class App(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super(App, self).__init__(parent)
            uic.loadUi("designUI.ui", self)
    
            self.filename = ""
            self.converted_file = None
    
            thread = QtCore.QThread(self)
            thread.start()
            self.pdf_object = PdfObject()
            self.pdf_object.moveToThread(thread)
            self.pdf_object.maximumChanged.connect(self.progressBar.setMaximum)
            self.pdf_object.progressChanged.connect(self.progressBar.setValue)
            self.pdf_object.pandasChanged.connect(self.on_pandasChanged)
    
            self.pushButton.clicked.connect(self.openFileNameDialog)
            self.pushButton_3.clicked.connect(self.convert)
            self.pushButton_2.clicked.connect(self.view)
            self.pushButton_4.clicked.connect(self.saveFileDialog)
    
        def openFileNameDialog(self):
            options = QtWidgets.QFileDialog.Options()
            options |= QtWidgets.QFileDialog.DontUseNativeDialog
            fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
                self, "Open File", "", "All Files (*);;Python Files (*.py)"
            )  # , options=options)
            if fileName:
                self.filename = fileName
    
        def view(self):
            if self.converted_file is not None:
                model = PandasModel(self.converted_file)
                self.tableView.setModel(model)
    
        def convert(self):
            if self.filename:
                wrapper = partial(self.pdf_object.pdf2excel, self.filename)
                QtCore.QTimer.singleShot(0, wrapper)
    
        @QtCore.pyqtSlot(pd.DataFrame)
        def on_pandasChanged(self, df):
            self.converted_file = df.copy()
    
        def saveFileDialog(self):
            options = QtWidgets.QFileDialog.Options()
            options |= QtWidgets.QFileDialog.DontUseNativeDialog
            fileName, _ = QtWidgets.QFileDialog.getSaveFileName(
                self, "Save File", "", ".xls(*.xls)"
            )
            if fileName and self.converted_file is not None:
                self.converted_file.to_excel(fileName)
                msg = QtWidgets.QMessageBox()
                msg.setText("File is Saved")
                msg.setStandardButtons(QtWidgets.QMessageBox.Ok)
                # msg.buttonClicked.connect(msgbtn)
                msg.exec_()
    
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        app.setWindowIcon(QtGui.QIcon("pdf-to-excel-icon.png"))
        ex = App()
        ex.show()
        sys.exit(app.exec_())