PyQt - Modify GUI from another thread - python

I'm trying to modify my main layout from another thread. But the function run() is never called
and i'm having the error:
QObject::setParent: Cannot set parent, new parent is in a different
thread
Here's my code:
class FeedRetrievingThread(QtCore.QThread):
def __init__(self, parent=None):
super(FeedRetrievingThread, self).__init__(parent)
self.mainLayout = parent.mainLayout
def run(self):
# Do things with self.mainLayout
class MainWindow(QtGui.QDialog):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.mainLayout = QtGui.QGridLayout()
self.setLayout(self.mainLayout)
self.feedRetrievingThread = FeedRetrievingThread(self)
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.updateFeed)
self.timer.start(1000)
def updateFeed(self):
if not self.feedRetrievingThread.isRunning():
print 'Running thread.'
self.feedRetrievingThread.start()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
I really don't get it, why is it so difficult to access the GUI with PyQt?
In C# you have Invoke. Is there anything of the kind in PyQt?
I tried creating the thread directly from MainWindow.__init__ (without using the timer) but it didn't work either.

In Qt you should never attempt to directly update the GUI from outside of the GUI thread.
Instead, have your threads emit signals and connect them to slots which do the necessary updating from within the GUI thread.
See the Qt documentation regarding Threads and QObjects.

Related

Qt GUI inside loop?

What I want to achieve is a Qt Widget loop.
Simple example:
UI_dialog is a QDialog and after accepted it will open UI_mainwindow which is a QMainWindow.
There is a button in UI_mainwindow and if clicked, it will close UI_mainwindow and go back to UI_dialog.
What I've done so far:
I've tried:
create while loop in a Qthread which contains the two UI objects call UI_dialog inside UI_mainwindow (kind of succeed but may crash sometimes for my poor design)
In a GUI you must avoid the while True, the GUI already has an internal while True that allows you to listen to the events and according to it do the internal tasks. On the other hand the threads should be your last option since the GUI should not update from another thread directly, it should only be used if there is a blocking task.
In the case of Qt there are signals that allow notification of changes, this will be connected to functions so that the latter is invoked when the signal is emited.
from PyQt5 import QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.button = QtWidgets.QPushButton("Press me")
self.setCentralWidget(self.button)
self.button.clicked.connect(self.close)
class Dialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
buttonBox = QtWidgets.QDialogButtonBox()
buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(buttonBox)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w1 = MainWindow()
w2 = Dialog()
w1.button.clicked.connect(w2.show)
w2.accepted.connect(w1.show)
w2.show()
sys.exit(app.exec_())

Calling method after init doesn't show application

I have a simple PySide Gui which I want to show, however there currently is a blocking task which is a long process, populating the dropdown list with items. Which means the UI does not show until the dropdown list is finished being populated. I want to know is there a way to force show the UI before it attempts to populate the list. I would prefer the dialog show so users know they opened the tool before assuming it's crashed or something.
I'm using Qt since my application needs to run in both PySide and PySide2. I was initially trying to use the qApp.processEvents() but it doesn't seem to be available in the Qt wrapper, or I may have been missing something. If you know what the equivalent would be, I'm fine with the process events being the solution. Optionally if there is an elegant way to populate the list from a background thread somehow...
from Qt import QtGui, QtCore, QtWidgets
class Form(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.resize(200,50)
self.items = QtWidgets.QComboBox()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.items)
self.setLayout(layout)
# init
self.long_process()
def long_process(self):
for i in range(30000):
self.items.addItem('Item {}'.format(i))
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
form = Form()
form.show()
sys.exit(app.exec_())
A good option for these cases is always to use QTimer:
class Form(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
[...]
# init
timer = QtCore.QTimer(self)
timer.timeout.connect(self.on_timeout)
timer.start(0)
def on_timeout(self):
self.items.addItem('Item {}'.format(self.counter))
self.counter += 1
if self.counter == 30000:
self.sender().stop()

Python crashes with 2 worker threads in PyQt5 application

I would like to have 2 worker threads in my application. One should start running as soon as the GUI is loaded and another one should start later by some signal. Let's say it's a button click.
I came across a weird behavior when my Python interpreter crashes(as in shows me Windows error "Python stopped working", no stack trace) on executing the second thread.
Here is an example, that will crash right after I click the button.
class Worker(QtCore.QThread):
def __init__(self, method_to_run):
super().__init__()
self.method = method_to_run
def run(self):
self.method()
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
self.button = QPushButton('Test', self)
self.label = QLabel(self)
self.button.clicked.connect(self.handleButton)
layout = QVBoxLayout(self)
layout.addWidget(self.label)
layout.addWidget(self.button)
self.worker = Worker(self.test_method)
self.worker.start()
def handleButton(self):
self.label.setText('Button Clicked!')
worker = Worker(self.test_method)
worker.start()
#staticmethod
def test_method():
res = [i*i for i in range(100500)]
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
What's more weird is that it doesn't crash when you debug the application for some reason.
What am I missing here?
Edit
I could get much from the crashdump, as I don't have symbols for QT. But it looks like the crash happens inside QtCore.dll
ExceptionAddress: 00000000632d4669 (Qt5Core!QThread::start+0x0000000000000229)
The problem is that you didn't save a reference to the thread so it was deleted as soon as you exited handleButton. If you save a reference, that begs the question of how to handle its lifespan.
QThread is more than just a wrapper to a system thread - it implements other services that let you wire a thread into your GUI. You can use its finished handler to signal the widget when it terminates to do any cleanup.
In this example, I save the worker as self.worker2 and block starting the worker a second time until the first one completes.
import PyQt5
import PyQt5.QtCore as QtCore
from PyQt5.QtWidgets import *
import time
class Worker(QtCore.QThread):
def __init__(self, method_to_run):
super(Worker, self).__init__()
self.method = method_to_run
def run(self):
self.method()
class Window(QWidget):
def __init__(self):
super().__init__()
self.button = QPushButton('Test', self)
self.label = QLabel(self)
self.button.clicked.connect(self.handleButton)
layout = QVBoxLayout(self)
layout.addWidget(self.label)
layout.addWidget(self.button)
self.worker = Worker(self.test_method)
self.worker.start()
self.worker2 = None
def handleButton(self):
self.label.setText('Button Clicked!')
# likely better to disable the button instead... but
# this shows events in action.
if self.worker2:
self.label.setText('Worker already running')
else:
self.worker2 = Worker(self.test_method)
self.worker2.finished.connect(self.handle_worker2_done)
self.worker2.start()
def handle_worker2_done(self):
self.worker2 = None
self.label.setText('Worker done')
#staticmethod
def test_method():
#res = [i*i for i in range(100500)]
time.sleep(3)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
Your click triggered thread isn't actually trying to do work in a thread. You called the method, rather than passing the method as an argument, so you're trying to use the return value of test_method (None) as the method to run.
Change:
worker = Worker(self.test_method()) # Calls test_method and tries to run None
to:
worker = Worker(self.test_method) # Don't call test_method

How to prevent the GUI from freezing using QThread?

I have a GUI which needs to perform work that takes some time and I want to show the progress of this work, similar to the following:
import sys
import time
from PyQt4 import QtGui, QtCore
class MyProgress(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
# start loop with signal
self.button = QtGui.QPushButton('loop', self)
self.connect(self.button, QtCore.SIGNAL('clicked()'), self.loop)
# test button
self.test_button = QtGui.QPushButton('test')
self.connect(self.test_button, QtCore.SIGNAL('clicked()'), self.test)
self.pbar = QtGui.QProgressBar(self)
self.pbar.setMinimum(0)
self.pbar.setMaximum(100)
# layout
vbox = QtGui.QVBoxLayout()
vbox.addWidget(self.test_button)
vbox.addWidget(self.button)
vbox.addWidget(self.pbar)
self.setLayout(vbox)
self.show()
def update(self):
self.pbar.setValue(self.pbar.value() + 1)
def loop(self):
for step in range(100):
self.update()
print step
time.sleep(1)
def test(self):
if self.test_button.text() == 'test':
self.test_button.setText('ok')
else:
self.test_button.setText('test')
app = QtGui.QApplication(sys.argv)
view = MyProgress()
view.loop() # call loop directly to check whether view is displayed
sys.exit(app.exec_())
When I execute the code the loop method is called and it prints out the values as well as updates the progress bar. However the view widget will be blocked during the execution of loop and although this is fine for my application it doesn't look nice with Ubuntu. So I decided to move the work to a separate thread like this:
import sys
import time
from PyQt4 import QtGui, QtCore
class Worker(QtCore.QObject):
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
def loop(self):
for step in range(10):
print step
time.sleep(1)
class MyProgress(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
# test button
self.test_button = QtGui.QPushButton('test')
self.connect(self.test_button, QtCore.SIGNAL('clicked()'), self.test)
self.pbar = QtGui.QProgressBar(self)
self.pbar.setMinimum(0)
self.pbar.setMaximum(100)
# layout
vbox = QtGui.QVBoxLayout()
vbox.addWidget(self.test_button)
vbox.addWidget(self.pbar)
self.setLayout(vbox)
self.show()
def test(self):
if self.test_button.text() == 'test':
self.test_button.setText('ok')
else:
self.test_button.setText('test')
app = QtGui.QApplication(sys.argv)
view = MyProgress()
work = Worker()
thread = QtCore.QThread()
work.moveToThread(thread)
# app.connect(thread, QtCore.SIGNAL('started()'), work.loop) # alternative
thread.start()
work.loop() # not called if thread started() connected to loop
sys.exit(app.exec_())
When I run this version of the script the loop starts running (the steps are displayed in the terminal) but the view widget is not shown. This is the first thing I can't quite follow. Because the only difference from the previous version here is that the loop runs in a different object however the view widget is created before and therefore should be shown (as it was the case for the previous script).
However when I connected the signal started() from thread to the loop function of worker then loop is never executed although I start the thread (in this case I didn't call loop on worker). On the other hand view is shown which makes me think that it depends whether app.exec_() is called or not. However in the 1st version of the script where loop was called on view it showed the widget although it couldn't reach app.exec_().
Does anyone know what happens here and can explain how to execute loop (in a separate thread) without freezing view?
EDIT: If I add a thread.finished.connect(app.exit) the application exits immediately without executing loop. I checked out the 2nd version of this answer which is basically the same what I do. But in both cases it finishes the job immediately without executing the desired method and I can't really spot why.
The example doesn't work because communication between the worker thread and the GUI thread is all one way.
Cross-thread commnunication is usually done with signals, because it is an easy way to ensure that everything is done asynchronously and in a thread-safe manner. Qt does this by wrapping the signals as events, which means that an event-loop must be running for everything to work properly.
To fix your example, use the thread's started signal to tell the worker to start working, and then periodically emit a custom signal from the worker to tell the GUI to update the progress bar:
class Worker(QtCore.QObject):
valueChanged = QtCore.pyqtSignal(int)
def loop(self):
for step in range(0, 10):
print step
time.sleep(1)
self.valueChanged.emit((step + 1) * 10)
...
thread = QtCore.QThread()
work.moveToThread(thread)
thread.started.connect(work.loop)
work.valueChanged.connect(view.pbar.setValue)
thread.start()
sys.exit(app.exec_())

Multithreading with pyqt - Can't get the separate threads running at the same time?

I am trying to get a PyQT GUI running ontop of my python application and I have tried to get it separated into 2 threads so the GUI would be responsive while my main running loop goes, but I have not been able to get it going. Maybe I am misunderstanding it. Here is what I've tried:
My Window and Worker thread are defined as follows:
class Window(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self, parent)
self.thread = Worker()
start = QPushButton("Start", self)
QObject.connect(start, SIGNAL("clicked()"), MAIN_WORLD.begin)
hbox = QVBoxLayout(self)
hbox.addStretch(4)
class Worker(QThread):
def __init__(self, parent = None):
QThread.__init__(self, parent)
if __name__ == '__main__':
MAIN_WORLD = World()
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
which seems to follow very closely to online examples. My World class is running a loop that is infinite once the user clicks "Start" until it's clicked again. Here is part of the definition of it.
class World(QThread):
def __init__(self, parent = None):
QThread.__init__(self, parent)
self.currentlyRunning = False
//snip
def begin(self):
if self.currentlyRunning:
self.currentlyRunning = False
else:
self.currentlyRunning = True
self.MethodThatDoesUsefulStuff()
edit: I have noticed that I'm not really "using" my worker thread. How do I create my world thread as a worker thread?
After looking more closely at your code, you have MAIN_WORLD started before QApplication, which isn't what you want.
You want to do something like this:
if __name__ == '__main__':
app = QApplication(sys.argv)
sys.exit(app.exec_())
And, in your Window class:
class Window(QWidget):
def __init__(self, *args):
self.world_thread = World();
# ...
The above will allow the main thread to control the gui and allow the worker threads to work in the background.
Granted that is PyQt and not PySide, but:
Don't subclass QThread, subclass QObject (see http://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/).
Then the basic workflow is to create a new thread, move your worker into the thread, start the thread and your worker. Your problem may be that you never actually start your new thread, initialization won't do that - then both your GUI and your worker are running the same thread, as others have commented. The GIL doesn't really enter into the picture. Check out the QThread docs: http://doc.qt.io/qt-4.8/qthread.html.

Categories