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
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.
PyQt6 Crash Course — a new tutorial in your Inbox every day
Beginner-focused crash course explaining the basics with hands-on examples.
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.
- 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?!
Create GUI Applications with Python & Qt5 by Martin Fitzpatrick — (PyQt5 Edition) The hands-on guide to making apps with Python — Over 10,000 copies sold!