Nader
Nader

Reputation: 690

Progress Bar issues in PyQt4

I have developed a GUI that is used to extract some data from a file. This process takes a long time. During the time when the program is processing the outside file (using subprocess.Popen), the main GUI will not be responsive (as expected). I wanted to add a message with an oscillating progress bar to the user to let him know the program is working and just to hang in there. I implemented the progress bar as a QDialog and then call it before I call the long process. If I call the progress bar using the [dialog.exec_()] method, then the program waits for my response which is the outcome of the dialog. If I close the dialog the program continues on without the progress message I want to show. If I use the [dialog.show()] method, then all I see is an empty white window where the progress bar should be.
I tried multiple variations on this but to no success.

I am using:

Python 2.7.2
PyQt4: 4.9.1
Windows 7

Here is a sample code that shows the problem. Switch between dialog.exec() and dialog.show() to demo the problem.


import sys
import time
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import pyqtSignature

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    _fromUtf8 = lambda s: s

class LongProcess(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(LongProcess, self).__init__(parent)
        self.setup(self)

    def setup(self, MainWindow):
        MainWindow.setObjectName(_fromUtf8("MainWindow"))
        MainWindow.resize(255, 208)
        MainWindow.setMinimumSize(QtCore.QSize(300, 208))
        MainWindow.setMaximumSize(QtCore.QSize(300, 208))
        self.centralwidget = QtGui.QWidget(MainWindow)
        self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
        self.verticalLayout = QtGui.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
        self.textEdit = QtGui.QTextEdit(self.centralwidget)
        self.textEdit.setObjectName(_fromUtf8("textEdit"))
        self.verticalLayout.addWidget(self.textEdit)
        self.pushButton = QtGui.QPushButton(self.centralwidget)
        self.pushButton.setObjectName(_fromUtf8("pushButton"))
        self.verticalLayout.addWidget(self.pushButton)
        MainWindow.setCentralWidget(self.centralwidget)

        MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
        self.textEdit.setHtml(QtGui.QApplication.translate("MainWindow", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
                                                           "<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
                                                           "p, li { white-space: pre-wrap; }\n"
                                                           "</style></head><body style=\" font-family:\'MS Shell Dlg 2\'; font-size:8.25pt; font-weight:400; font-style:normal;\">\n"
                                                           "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:8pt;\">We want to run a long process in the background (calling a subprocess) and use an oscillating progress bar to show that the process is running. </span></p>\n"
                                                           "<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;\"><br /></p>\n"
                                                           "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:8pt;\">When long process ends, we will close the oscillating progress bar. </span></p></body></html>", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButton.setText(QtGui.QApplication.translate("MainWindow", "Run Long Process", None, QtGui.QApplication.UnicodeUTF8))
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    @pyqtSignature("")
    def on_pushButton_clicked(self):
        '''
            Simulate some long process to be performed.
            Before Long Process starts, show an oscillating progress bar to
            indiate process is running in background. 
        '''
        dialog = QtGui.QDialog()    
        progressBar = Ui_porcessProgress()
        progressBar.setupUi(dialog)
        dialog.show()
#        dialog.exec_()

        start = time.time()
        diff = 0
        while diff < 10:
            end = time.time()
            diff = end - start
            print (diff)

class Ui_porcessProgress(object):
    def setupUi(self, porcessProgress):
        porcessProgress.setObjectName(_fromUtf8("porcessProgress"))
        porcessProgress.setWindowModality(QtCore.Qt.ApplicationModal)
        porcessProgress.resize(329, 81)
        porcessProgress.setMinimumSize(QtCore.QSize(329, 81))
        porcessProgress.setMaximumSize(QtCore.QSize(329, 81))
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap(_fromUtf8(":/WFT/wftlogo2.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        porcessProgress.setWindowIcon(icon)
        porcessProgress.setModal(True)
        self.verticalLayout = QtGui.QVBoxLayout(porcessProgress)
        self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
        self.label = QtGui.QLabel(porcessProgress)
        self.label.setObjectName(_fromUtf8("label"))
        self.verticalLayout.addWidget(self.label)
        self.porcessProgressBar = QtGui.QProgressBar(porcessProgress)
        self.porcessProgressBar.setMaximum(0)
        self.porcessProgressBar.setProperty("value", 10)
        self.porcessProgressBar.setTextVisible(False)
        self.porcessProgressBar.setObjectName(_fromUtf8("porcessProgressBar"))
        self.verticalLayout.addWidget(self.porcessProgressBar)
        porcessProgress.setWindowTitle(QtGui.QApplication.translate("porcessProgress", "Please wait...", None, QtGui.QApplication.UnicodeUTF8))
        self.label.setText(QtGui.QApplication.translate("porcessProgress", "Time History data extraction from ODB file in progress...", None, QtGui.QApplication.UnicodeUTF8))
        QtCore.QMetaObject.connectSlotsByName(porcessProgress)
#
#------------------------------------------------------------------------------ 
#
def main():
    app = QtGui.QApplication(sys.argv)
    mainWindow = LongProcess()
    mainWindow.show()
    sys.exit(app.exec_())
#
#------------------------------------------------------------------------------ 
#
if __name__ == "__main__":
    main()

Upvotes: 2

Views: 4696

Answers (4)

Ryan Kim
Ryan Kim

Reputation: 258

This is kinda quackery way to fix this issue.

If you want to do this right way, You should put while loop inside of QThread, and using Signal and slot to update QProgressbar.

But this will fix your issue just fine for now.

Missing pieces were,

  1. set maximum value of progressBar setMaximum(value)
  2. add code updating progressBar setValue(value)
  3. Enforce Event Process using QApplication.processEvents()

Quick fix :

#Add QApplication on top #################
from PyQt4.QtGui import QApplication
##########################################
def on_pushButton_clicked(self):
        '''
            Simulate some long process to be performed.
            Before Long Process starts, show an oscillating progress bar to
            indiate process is running in background. 
        '''
        dialog = QtGui.QDialog()    
        progressBar = Ui_porcessProgress()
        progressBar.setupUi(dialog)
        dialog.show()
        #dialog.exec_()

        start = time.time()
        diff = 0
        while diff < 10:
            end = time.time()
            diff = end - start


            ################################################### 
            #add routine to update progressBar value
            progressBar.porcessProgressBar.setValue(diff)
            QApplication.processEvents()
            #add routine to update progressBar value 
            ###################################################


            print (diff)

class Ui_porcessProgress(object):
    def setupUi(self, porcessProgress):
        porcessProgress.setObjectName(_fromUtf8("porcessProgress"))
        porcessProgress.setWindowModality(QtCore.Qt.ApplicationModal)
        porcessProgress.resize(329, 81)
        porcessProgress.setMinimumSize(QtCore.QSize(329, 81))
        porcessProgress.setMaximumSize(QtCore.QSize(329, 81))
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap(_fromUtf8(":/WFT/wftlogo2.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        porcessProgress.setWindowIcon(icon)
        porcessProgress.setModal(True)
        self.verticalLayout = QtGui.QVBoxLayout(porcessProgress)
        self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
        self.label = QtGui.QLabel(porcessProgress)
        self.label.setObjectName(_fromUtf8("label"))
        self.verticalLayout.addWidget(self.label)
        self.porcessProgressBar = QtGui.QProgressBar(porcessProgress)

        ###########################################
        ####Set max value of progress bar
        self.porcessProgressBar.setMaximum(10)
        ############################################

        self.porcessProgressBar.setProperty("value", 10)
        self.porcessProgressBar.setTextVisible(False)
        self.porcessProgressBar.setObjectName(_fromUtf8("porcessProgressBar"))
        self.verticalLayout.addWidget(self.porcessProgressBar)
        porcessProgress.setWindowTitle(QtGui.QApplication.translate("porcessProgress", "Please wait...", None, QtGui.QApplication.UnicodeUTF8))
        self.label.setText(QtGui.QApplication.translate("porcessProgress", "Time History data extraction from ODB file in progress...", None, QtGui.QApplication.UnicodeUTF8))
        QtCore.QMetaObject.connectSlotsByName(porcessProgress)
#
#------------------------------------------------------------------------------ 

Upvotes: 2

Cryptite
Cryptite

Reputation: 1466

A ProgressBar that is looped through the main thread will lock the very same thread that is running your GUI, thus you won't see any updates until the LongProcess is finished. You'll need to thread the part doing the long process and report back to the main thread. Take a look at this SO question

Upvotes: 0

mata
mata

Reputation: 69032

everything you do in the on_pushButton_clicked method will be executed in the gui thread, and therefore freeze the gui. just move it to a QThread. and read this.

Upvotes: 0

Mat
Mat

Reputation: 206689

You need to return from the button slot for GUI updates to happen. If you sit in that slot, you're blocking the UI thread so actual (visible) widget setup will not be show.

I'm not familiar with Python, but the Qt way of running processes in the background (which won't block your UI) is to use QProcess. The QProcess instance has signals to notify of the child process's state changes, and methods to handle the input and output streams. You might want to look into that, it will allow for more flexibility.
(In particular, you don't need to deal with threading at all which is, IMO, quite a good thing.)

Upvotes: 0

Related Questions