Updating progressbar from QRunner

Heads up! You've already completed this tutorial.

Michal_Plichta | 2020-05-11 07:01:24 UTC | #1

Nice! But I would like to in progress_fn() method update self.progressbar widget form MainWindow! How to deal with: QObject::setParent: Cannot set parent, new parent is in a different thread.


martin | 2021-04-30 12:42:08 UTC | #2

Hey @Michal_Plichta it's difficult to be sure without seeing the code, but the error sounds like you're attempting to create the progressbar from in your thread? You can't do this -- all your widgets need to be on the main GUI thread, and stay there.

The approach to use is to create a persistent progressbar on your main window status bar (or anywhere else) and then emit only the progress from the runner. This should be emitted as a simple number -- e.g. a int between 0 and 100, or a float between 0 and 1.

Here is a minimal working example

python
from PyQt5.QtWidgets import (
    QWidget, QApplication, QProgressBar, QMainWindow
)

from PyQt5.QtCore import (
    Qt, QObject, pyqtSignal, pyqtSlot, QRunnable, QThreadPool
)
import time


class WorkerSignals(QObject):

    progress = pyqtSignal(int)



class JobRunner(QRunnable):

    signals = WorkerSignals()

    def __init__(self):
        super().__init__()

    @pyqtSlot()
    def run(self):
        for n in range(100):
            self.signals.progress.emit(n + 1)
            time.sleep(0.1)



class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()

        # Create a statusbar.
        self.status = self.statusBar()
        self.progress = QProgressBar()
        self.status.addPermanentWidget(self.progress)

        # Thread runner
        self.threadpool = QThreadPool()

        # Create a runner
        self.runner = JobRunner()
        self.runner.signals.progress.connect(self.update_progress)
        self.threadpool.start(self.runner)

        self.show()

    def update_progress(self, n):
        self.progress.setValue(n)

app = QApplication([])
w = MainWindow()
app.exec_()

When you run this you'll see a window pop up, with a progress bar moving along from 0 to 100 (increasing by 1 every 1/10 of a second). The updates are coming as int values from the single JobRunner object we create and add to the threadpool in the window init.

Over 10,000 developers have bought Create GUI Applications with Python & Qt!
Create GUI Applications with Python & Qt5
Take a look

Downloadable ebook (PDF, ePub) & Complete Source code

Also available from Leanpub and Amazon Paperback

[[ discount.discount_pc ]]% OFF for the next [[ discount.duration ]] [[discount.description ]] with the code [[ discount.coupon_code ]]

Purchasing Power Parity

Developers in [[ country ]] get [[ discount.discount_pc ]]% OFF on all books & courses with code [[ discount.coupon_code ]]

progress|202x139


Eolinwen | 2020-05-12 16:25:20 UTC | #3

@Michal_Plichta

Great and very good question useful for a lot of people. :smile:

By PM I have asked to Martin for making a tutorial about progressBar and even more. I'm going to search this one and open a PR.

@martin

You have this ability (which I don't have ) to simply explain complicated things so that we can understand them from the beginning.


Michal_Plichta | 2021-04-30 11:28:01 UTC | #4

Really thx for reply! Sorry, I didn't saw you answer. I did some mailbox cleanup and see notification. Yes, I made similar approach (it's working), keep updating widgets (change labels` text, enabling buttons etc.) only in MainThread and emit progressbar's progress via signals.

But I have other things one small issue and question. 1. Issue: If you look at your example above JobRunner.run(), where you just sleeping to simulate long task. In my case is just one external function call (which I can not change and it take 25-30 sec to finish). So I can emit 10% progress before call and 90% after function finish. I thinking how update progressbar during execution of this function smoothly let say from 10% to 90% and then just emit 100% when function is done.

  1. Question: I have other case when background long test have 3 different functions and between each one I need changing labels text, enabling buttons, change some icons, basically updating some widgets. Of course I need do it in MainThread. So, I did split them to separate JobRunners or Workers (from your original article) and callback method of finished signal update widgets (since it run in MainThread) and starts next Worker test. It is working but I thinking is better/nicer solution?!

Packaging Python Applications with PyInstaller by Martin Fitzpatrick — This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.

More info Get the book

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak

Updating progressbar from QRunner was written by Martin Fitzpatrick .

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt.