Updating Gui Elements in Multithreaded Pyqt

Updating GUI elements in MultiThreaded PyQT

Here some very basic examples.


You can pass references to GUI elements to threads, and update them in thread.

import sys
import urllib2

from PyQt4 import QtCore, QtGui


class DownloadThread(QtCore.QThread):
def __init__(self, url, list_widget):
QtCore.QThread.__init__(self)
self.url = url
self.list_widget = list_widget

def run(self):
info = urllib2.urlopen(self.url).info()
self.list_widget.addItem('%s\n%s' % (self.url, info))


class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.list_widget = QtGui.QListWidget()
self.button = QtGui.QPushButton("Start")
self.button.clicked.connect(self.start_download)
layout = QtGui.QVBoxLayout()
layout.addWidget(self.button)
layout.addWidget(self.list_widget)
self.setLayout(layout)

def start_download(self):
urls = ['http://google.com', 'http://twitter.com', 'http://yandex.ru',
'http://stackoverflow.com/', 'http://www.youtube.com/']
self.threads = []
for url in urls:
downloader = DownloadThread(url, self.list_widget)
self.threads.append(downloader)
downloader.start()

if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())


Editors Note: Qt widgets are not thread safe and should not be accessed from any thread but the main thread (see the Qt documentation for more details). The correct way to use threads is via signals/slots as the second part of this answer shows.


Also, you can use signals and slots, to separate gui and network logic.

import sys
import urllib2

from PyQt4 import QtCore, QtGui


class DownloadThread(QtCore.QThread):

data_downloaded = QtCore.pyqtSignal(object)

def __init__(self, url):
QtCore.QThread.__init__(self)
self.url = url

def run(self):
info = urllib2.urlopen(self.url).info()
self.data_downloaded.emit('%s\n%s' % (self.url, info))


class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.list_widget = QtGui.QListWidget()
self.button = QtGui.QPushButton("Start")
self.button.clicked.connect(self.start_download)
layout = QtGui.QVBoxLayout()
layout.addWidget(self.button)
layout.addWidget(self.list_widget)
self.setLayout(layout)

def start_download(self):
urls = ['http://google.com', 'http://twitter.com', 'http://yandex.ru',
'http://stackoverflow.com/', 'http://www.youtube.com/']
self.threads = []
for url in urls:
downloader = DownloadThread(url)
downloader.data_downloaded.connect(self.on_data_ready)
self.threads.append(downloader)
downloader.start()

def on_data_ready(self, data):
print data
self.list_widget.addItem(unicode(data))


if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())

Update PyQt GUI from a Python thread

Define a custom signal that sends updates to the progress-bar:

class Form(QMainWindow):
finished = pyqtSignal()
updateProgress = pyqtSignal(int)

def __init__(self, parent=None):
super(Form, self).__init__(parent)
...
self.updateProgress.connect(self.ui.progressBar.setValue)

def run_test(self):
for i in range(100):
per = i + 1
self.updateProgress.emit(per)
...

Updating Python GUI element from Qthread

There are a few things wrong with your code, however you were pretty close.

The first obvious one is that you have a loop with a time.sleep() in your main thread. The Ui_Dialog.get_time() method runs in the main thread (as it should). You should not have any long running code there. However, the loop with the time.sleep(2) in it is long running code. As it stands now, your GUI locks up because control it not returned to the GUI event loop for 2*countto seconds. Just remove the whole while loop. I don't really know why it is there.

Delete this:

q = 0
while q < 5:
print(countto+2)
time.sleep(2)
q += 1

The next issue comes because you recreate the QThread each time you click the button. As such, for each new object, you need to connect the updateProgress signal in that object to the setProgress slot. So change the code there to:

self.wt = WorkerThread(countto)        
self.wt.updateProgress.connect(self.setProgress)
self.wt.start()

At this point you will see the progress bar update correctly. However, the maximum value of the progress bar is set to 100 by default. So you might want to set the maximum value to countto just before you create the thread. For example:

self.progressBar.setMaximum(countto)
self.wt = WorkerThread(countto)

Hope that explains things!

Easy Multi-threading with PyQt5, for updating QTextBrowser contents

You do not have to update the GUI from an external thread. There are several options like signals, QMetaObject::invokeMethod(...), QEvent and QTimer::singleShot(0, ...) with pyqtSlot.

Using the last method the solution is as follows:

from functools import partial
from PyQt5.QtCore import pyqtSlot

class MyApp(QMainWindow):
# ...

@pyqtSlot()
def stop_loader(self):
self.ui.loader.hide()
self.loading_animate.stop()

def get_response(self, text):
plain_text, speech = get_Wresponse(text)
QtCore.QTimer.singleShot(0, self.stop_loader)
wrapper = partial(self.ui.textDisplay.setText, plain_text)
QtCore.QTimer.singleShot(0, wrapper)
if speech == '':
say("Here you GO!")
else:
say(speech)

def load_response(self):
self.start_loader()
text = self.ui.queryBox.displayText()
_thread.start_new_thread(self.get_response, (text,))

Multithreading updating with multiple windows in pyqt5

Why is it necessary to use multi-threading? The threads are not the magic solution, and on the contrary if their disadvantages are not known then they bring more problems than solutions. In your case it is unnecessary as you don't have any time consuming task that blocks the GUI. Bottom line: Use tools when necessary, not when you want.

On the other hand you are creating GUI from another thread which Qt prohibits since it is not thread-safe.

class AnotherWindow(QWidget):
def __init__(self):
super().__init__()
self.x = 0
layout = QVBoxLayout()
self.label = QLabel("Another Window")
layout.addWidget(self.label)
self.line = QLineEdit(self)
layout.addWidget(self.line)
self.setLayout(layout)
timer = QTimer(self)
timer.timeout.connect(self.update_line)
timer.start()

def update_line(self):
self.x += 1
self.line.setText(str(self.x))


class AnotherWindow2(QWidget):
def __init__(self):
super().__init__()
self.x = 0
layout = QVBoxLayout()
self.label = QLabel("Another Window2")
layout.addWidget(self.label)
self.line = QLineEdit(self)
layout.addWidget(self.line)
self.setLayout(layout)
timer = QTimer(self)
timer.timeout.connect(self.update_line)
timer.start()

def update_line(self):
self.x += 3
self.line.setText(str(self.x))


class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
l = QVBoxLayout()
button1 = QPushButton("Push for Window 1")
button1.clicked.connect(self.show_new_window)
l.addWidget(button1)

button2 = QPushButton("Push for Window 2")
button2.clicked.connect(self.show_new_window2)
l.addWidget(button2)

w = QWidget()
w.setLayout(l)
self.setCentralWidget(w)

def show_new_window(self):
self.w = AnotherWindow()
self.w.show()

def show_new_window2(self):
self.w2 = AnotherWindow2()
self.w2.show()


app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()

PyQt update gui

self.trigger.connect(Output().main())

This line is problematic. You are instantiating a class in the thread which looks like a widget. This is wrong. You shouldn't use GUI elements in a different thread. All GUI related code should run in the same thread with the event loop.

The above line is also wrong in terms of design. You emit a custom signal from your thread and this is a good way. But the object to process this signal should be the one that owns/creates the thread, namely your MainWindow

You also don't keep a reference to your thread instance. You create it in a method, but it is local. So it'll be garbage collected, you probably would see a warning that it is deleted before it is finished.

Here is a minimal working example:

import sys
from PyQt4 import QtGui, QtCore
import time
import random


class MyThread(QtCore.QThread):
trigger = QtCore.pyqtSignal(int)

def __init__(self, parent=None):
super(MyThread, self).__init__(parent)

def setup(self, thread_no):
self.thread_no = thread_no

def run(self):
time.sleep(random.random()*5) # random sleep to imitate working
self.trigger.emit(self.thread_no)


class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.text_area = QtGui.QTextBrowser()
self.thread_button = QtGui.QPushButton('Start threads')
self.thread_button.clicked.connect(self.start_threads)

central_widget = QtGui.QWidget()
central_layout = QtGui.QHBoxLayout()
central_layout.addWidget(self.text_area)
central_layout.addWidget(self.thread_button)
central_widget.setLayout(central_layout)
self.setCentralWidget(central_widget)

def start_threads(self):
self.threads = [] # this will keep a reference to threads
for i in range(10):
thread = MyThread(self) # create a thread
thread.trigger.connect(self.update_text) # connect to it's signal
thread.setup(i) # just setting up a parameter
thread.start() # start the thread
self.threads.append(thread) # keep a reference

def update_text(self, thread_no):
self.text_area.append('thread # %d finished' % thread_no)

if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)

mainwindow = Main()
mainwindow.show()

sys.exit(app.exec_())

PyQt: How to update progress without freezing the GUI?

If you want to use signals to indicate progress to the main thread then you should really be using PyQt's QThread class instead of the Thread class from Python's threading module.

A simple example which uses QThread, signals and slots can be found on the PyQt Wiki:

https://wiki.python.org/moin/PyQt/Threading,_Signals_and_Slots



Related Topics



Leave a reply



Submit