2

I am using a QProgressDialog to show the progress of a routine that is running. I found this very helpful question/answer how-to-show-a-moving-progress-bar-in-qgis, but I can not reproduce successful results using QGIS 3.0. I copy and paste the code into the python editor and

replace: from PyQt4.QtGui import QProgressDialog, QProgressBar
with: from PyQt5.QtWidgets import QProgressDialog, QProgressBar

also print sum with print (sum) and then run the script. The dialog displays with the window title ("Progress") and a blank dialog.

Can you provide a code sample of how to implement a progress bar in a dialog using QGIS 3.0?

below is the code I tried:

from PyQt4.QtGui import QProgressDialog, QProgressBar

def progdialog(progress):
    dialog = QProgressDialog()
    dialog.setWindowTitle("Progress")
    dialog.setLabelText("text")
    bar = QProgressBar(dialog)
    bar.setTextVisible(True)
    bar.setValue(progress)
    dialog.setBar(bar)
    dialog.setMinimumWidth(300)
    dialog.show()
    return dialog, bar

def calc(x, y):
    dialog, bar = progdialog(0)
    bar.setValue(0)
    bar.setMaximum(100)
    sum = 0
    progress = 0
    for i in range(x):
        for j in range(y):
            k = i + j
            sum += k
        i += 1
        progress = (float(i) / float(x)) * 100
        bar.setValue(progress)
    print sum

calc(10000, 2000)

1 Answer 1

12

For any task or dialog where you want to update a progress bar, I recommend using QgsTask and QgsTaskManager. This will allow the task to run in a background thread, and prevent your dialog window from freezing while the task is running. It also allows for cancelling of the task by the user at any time.

There are a few ways of using QgsTask and QgsTaskManager which are detailed here in the documentation.

Here is an example I have put together which updates a progress bar in a dialog window and shows how to stop the task with a cancel button from the dialog.

You can run this from the editor in the QGIS Python console.

from PyQt5.QtWidgets import *
import time

class TestTask(QgsTask):
    """Here we subclass QgsTask"""
    def __init__(self, desc):
        QgsTask.__init__(self, desc)

    def run(self):
        """This function is where you do the 'heavy lifting' or implement
        the task which you want to run in a background thread. This function 
        must return True or False and should only interact with the main thread
        via signals"""
        for i in range (21):
            time.sleep(0.5)
            val = i * 5
            #report progress which can be received by the main thread
            self.setProgress(val)
            #check to see if the task has been cancelled
            if self.isCanceled():
                return False
        return True

    def finished(self, result):
        """This function is called automatically when the task is completed and is
        called from the main thread so it is safe to interact with the GUI etc here"""
        if result is False:
            iface.messageBar().pushMessage('Task was cancelled')
        else:
            iface.messageBar().pushMessage('Task Complete')

class My_Dialog(QDialog):
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
        self.resize(500, 350)
        self.lbl_info = QLabel('Info:', self)
        self.lbl_info.move(100, 50)
        self.edit_info = QLineEdit(self)
        self.edit_info.move(200, 50)
        lbl_prog = QLabel('Task Progress: ', self)
        lbl_prog.move(100, 210)
        self.prog = QProgressBar(self)
        self.prog.resize(200, 30)
        self.prog.move(200, 200)
        btn_OK = QPushButton('OK', self)
        btn_OK.move(300, 300)
        btn_OK.clicked.connect(self.newTask)
        btn_close = QPushButton('Close',self)
        btn_close.move(400, 300)
        btn_close.clicked.connect(self.close_win)
        btn_cancel = QPushButton('Cancel Task', self)
        btn_cancel.move(50, 300)
        btn_cancel.clicked.connect(self.cancelTask)


    def newTask(self):
        """Create a task and add it to the Task Manager"""
        self.task = TestTask('Custom Task')
        #connect to signals from the background threads to perform gui operations
        #such as updating the progress bar
        self.task.begun.connect(lambda: self.edit_info.setText('Working...'))
        self.task.progressChanged.connect(lambda: self.prog.setValue(self.task.progress()))
        self.task.taskCompleted.connect(lambda: self.edit_info.setText('Task Complete'))
        self.task.taskTerminated.connect(self.TaskCancelled)
        QgsApplication.taskManager().addTask(self.task)

    def cancelTask(self):
        self.task.cancel()

    def TaskCancelled(self):
        self.prog.setValue(0)
        self.edit_info.setText('Task Cancelled')

    def close_win(self):
        My_Dialog.close(self)

m = My_Dialog()
m.show()

Edit:

Since your original question was about QProgressDialog, below is a minimal code snippet to show an updating QProgressDialog with a cancel button. This code works for me in the Python console editor in QGIS 3.4.1

from PyQt5.QtWidgets import *
import time

prog = QProgressDialog('Working...', 'Cancel', 0, 100)
prog.setWindowModality(Qt.WindowModal)
for i in range (1, 101):
    time.sleep(0.05)
    prog.setValue(i)
    if prog.wasCanceled():
        break
5
  • I'm sure this works, and thank you. After attempts to use QgsTask, QgsTask.fromFunction, QProgressDialog and many hours of adjusting my plugin, I have decided that QgsApplication.setOverrideCursor(Qt.WaitCursor) before the routine and QgsApplication.restoreOverrideCursor() after, is the only way I can reliably indicate QGIS is active. I am amazed that so much coding is required to run the process in the background just to show progress by a plugin. Commented Jan 10, 2019 at 19:41
  • 1
    You're welcome @DGreenewalt. I'm sorry to hear QgsTask is not working for your case. If you would like to try again with QProgressDialog, I have edited my answer to include an example of an updating progress dialog which works for me.
    – Ben W
    Commented Jan 11, 2019 at 9:29
  • I am still curious about what difficulties you experienced in trying to implement threading with QgsTask/ TaskManager; perhaps you could ask another question/s detailing the specific problems you encountered. Someone may be able to help.
    – Ben W
    Commented Jan 11, 2019 at 9:37
  • I copied and pasted your QProgressDialog code and it worked properly. The prog.setWindowModality(Qt.WindowModal) looks like the key I was missing. If I remove that line from your example, I get the exact behavior I was encountering before. I also needed a forceShow() before setting the modality. Now all is well, thank you so much. Commented Jan 11, 2019 at 16:43
  • You're most welcome. Glad it's working.
    – Ben W
    Commented Jan 12, 2019 at 8:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.