Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Busy indication with PyQt progress bar

I'm trying to write a script which will display a busy indication while performing the task. And when the task is over, the progress bar will fill to the end showing that 100% task has been completed. I just want the progress bar to show a task is going on.But when I start the task, busy indication stops.It seems to me that the indication and the task can not continue together. Please help me. Here's mycode:

from PyQt4 import QtCore, QtGui
from time import sleep
try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

try:
    _encoding = QtGui.QApplication.UnicodeUTF8
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig)

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName(_fromUtf8("MainWindow"))
        MainWindow.resize(344, 159)
        self.centralwidget = QtGui.QWidget(MainWindow)
        self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
        self.pb = QtGui.QProgressBar(self.centralwidget)
        self.pb.setGeometry(QtCore.QRect(20, 20, 301, 31))
        self.pb.setProperty("value", 0)
        self.pb.setObjectName(_fromUtf8("pb"))
        self.btn = QtGui.QPushButton(self.centralwidget)
        self.btn.setGeometry(QtCore.QRect(20, 70, 98, 27))
        self.btn.setObjectName(_fromUtf8("btn"))
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtGui.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 344, 25))
        self.menubar.setObjectName(_fromUtf8("menubar"))
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtGui.QStatusBar(MainWindow)
        self.statusbar.setObjectName(_fromUtf8("statusbar"))
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QObject.connect(self.btn, QtCore.SIGNAL(_fromUtf8("clicked()")), self.action)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)            

    def action(self):
        self.pb.setRange(0, 0)
        sleep(3) # Here I want to run a command.For example: os.system('copy something')
        self.pb.setRange(0, 100)
        self.pb.setValue(100)
        QtGui.qApp.processEvents()

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
        self.btn.setText(_translate("MainWindow", "Start", None))


if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    MainWindow = QtGui.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())
like image 345
user2420437 Avatar asked Oct 18 '13 05:10

user2420437


3 Answers

First, it's a bad idea to directly edit the code created with QtDesigner. You may have seen the line # WARNING! All changes made in this file will be lost! at the top of the document. For such a simple widget, you're better off with manual coding.

Secondly, take a closer look at what the action slot actually does.

def action(self):
    self.pb.setRange(0, 0) # Un
    sleep(3) # <-- Your slot blocks HERE
    self.pb.setRange(0, 100)
    self.pb.setValue(100)
    QtGui.qApp.processEvents()

There is no reason for your progressBar to update its value while your slot is blocked in sleep. When action is called, the slot thread sleeps for 3 sec then sets the progress bar to a full 100.

You can't expect the progressBar to magically update itself while your task is running. If you have no idea how long it will take and you can't subdivise it in steps, you should consider using a pulsed ProgressBar instead (see example 1 below). If you can easily get the progression of your task (say copying n files), you should update the value of your progressBar accordingly.

Either way, you should use QThread to get a non-blocking behaviour, and signals to communicate between your thread(s) and your main application.

  • The main application starts the QThread implementing the long-running task.
  • The QThread notifies the task progression (if available) or completion to the main application

Example 1 - Pulse ProgressBar:

If minimum and maximum are both set to 0, the progress bar will show a busy indicator instead of a percentage of steps.

class MyCustomWidget(QtGui.QWidget):

    def __init__(self, parent=None):
        super(MyCustomWidget, self).__init__(parent)
        layout = QtGui.QVBoxLayout(self)

        # Create a progress bar and a button and add them to the main layout
        self.progressBar = QtGui.QProgressBar(self)
        self.progressBar.setRange(0,1)
        layout.addWidget(self.progressBar)
        button = QtGui.QPushButton("Start", self)
        layout.addWidget(button)      

        button.clicked.connect(self.onStart)

        self.myLongTask = TaskThread()
        self.myLongTask.taskFinished.connect(self.onFinished)

    def onStart(self): 
        self.progressBar.setRange(0,0)
        self.myLongTask.start()

    def onFinished(self):
        # Stop the pulsation
        self.progressBar.setRange(0,1)


class TaskThread(QtCore.QThread):
    taskFinished = QtCore.pyqtSignal()
    def run(self):
        time.sleep(3)
        self.taskFinished.emit()  

Example 2 - Classic ProgressBar:

class MyCustomWidget(QtGui.QWidget):

    def __init__(self, parent=None):
        super(MyCustomWidget, self).__init__(parent)
        layout = QtGui.QVBoxLayout(self)       

        self.progressBar = QtGui.QProgressBar(self)
        self.progressBar.setRange(0,100)
        button = QtGui.QPushButton("Start", self)
        layout.addWidget(self.progressBar)
        layout.addWidget(button)

        button.clicked.connect(self.onStart)

        self.myLongTask = TaskThread()
        self.myLongTask.notifyProgress.connect(self.onProgress)


    def onStart(self):
        self.myLongTask.start()

    def onProgress(self, i):
        self.progressBar.setValue(i)


class TaskThread(QtCore.QThread):
    notifyProgress = QtCore.pyqtSignal(int)
    def run(self):
        for i in range(101):
            self.notifyProgress.emit(i)
            time.sleep(0.1)
like image 186
Yoann Quenach de Quivillic Avatar answered Nov 01 '22 16:11

Yoann Quenach de Quivillic


A bit late, but I've written detailed documentation on this very issue since a lot of people seem to face this problem.

Introduction to Progress Bars

like image 31
daegontaven Avatar answered Nov 01 '22 17:11

daegontaven


Finally I got what I wanted, though little bit editing needed . I just added this line to onFinished(): self.progressBar.setValue(1) to confirm 100% task completion. Here's the code:

from PyQt4 import QtCore, QtGui
from time import sleep
import sys, os

class MyCustomWidget(QtGui.QWidget):

    def __init__(self, parent=None):
        super(MyCustomWidget, self).__init__(parent)
        layout = QtGui.QVBoxLayout(self)

        # Create a progress bar and a button and add them to the main layout
        self.progressBar = QtGui.QProgressBar(self)
        self.progressBar.setRange(0,1)
        layout.addWidget(self.progressBar)
        button = QtGui.QPushButton("Start", self)
        layout.addWidget(button)      

        button.clicked.connect(self.onStart)

        self.myLongTask = TaskThread()
        self.myLongTask.taskFinished.connect(self.onFinished)

    def onStart(self): 
        self.progressBar.setRange(0,0)
        self.myLongTask.start()

    def onFinished(self):
        # Stop the pulsation
        self.progressBar.setRange(0,1)
        self.progressBar.setValue(1)


class TaskThread(QtCore.QThread):
    taskFinished = QtCore.pyqtSignal()
    def run(self):
        os.system('sudo apt-get install leafpad')
        self.taskFinished.emit() 

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    window = MyCustomWidget()
    window.resize(640, 480)
    window.show()
    sys.exit(app.exec_())
like image 24
user2420437 Avatar answered Nov 01 '22 16:11

user2420437