Updating GUI elements in MultiThreaded PyQT

49,010

Solution 1

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_())

Solution 2

While reclosedev's answer worked perfectly for me, I was getting a QThread: Destroyed while thread is still running error. To correct this, I passed the parent class reference to the QThread constructor, as indicated in this question.

import sys
import urllib2

from PyQt4 import QtCore, QtGui


class DownloadThread(QtCore.QThread):

    data_downloaded = QtCore.pyqtSignal(object)

    def __init__(self, parent, url):
        QtCore.QThread.__init__(self, parent)
        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(parent=self, 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_())
Share:
49,010

Related videos on Youtube

Nuncjo
Author by

Nuncjo

Updated on November 18, 2021

Comments

  • Nuncjo
    Nuncjo over 2 years

    I was researching for some time to find information how to do multithreaded program using PyQT, updating GUI to show the results.

    I'm used to learning by example and i can't find (yes i was looking for weeks) any simple example of program using multithreading doing such simple task as for example connecting to list of www sites (5 threads) and just printing processed urls with response code.

    Could anyone share code or send me to good tutorial where such program is explained ?

  • Nuncjo
    Nuncjo about 12 years
    I will analyze this examples. Thank You.
  • Whatang
    Whatang about 12 years
    +1 for suggesting using signals to separate the threading from the display logic.
  • Nuncjo
    Nuncjo about 12 years
    Examples are great that's what i was looking for but how to limit threads number to 5 working simultaneously ?
  • reclosedev
    reclosedev about 12 years
    @MikeJanson, take a look at QThreadPool and its maxThreadCount property
  • Nuncjo
    Nuncjo about 12 years
    Uhmm.. i'm sorry but im to green in C++ to translate this to Python. That's one of the reason of looking the answer in stackoverflow.
  • reclosedev
    reclosedev about 12 years
    @MikeJanson, maybe PySide documentation will be simplier QThreadPool
  • Nuncjo
    Nuncjo about 12 years
    Hmm ok i understood something. I'm changing QThread to QRunnable in second example, then trying to start it by QtCore.QThreadPool.globalInstance().start(downloader) gives me TypeError: pyqtSignal must be bound to a QObject, not 'DownloadThread'
  • reclosedev
    reclosedev about 12 years
    @MikeJanson, Yes, I've tried to write example of using QThreadPool and encountered the same problem. Your class should inherit QObject to use signals (QRunable - doesn't), but then QThreadPool will not accept it. Here's example with workaround: pastebin.com/npnwenYw
  • Nuncjo
    Nuncjo about 12 years
    I found a possible solution (kedeligdata.blogspot.com/2010/01/…) but can't put it in this example.
  • Nuncjo
    Nuncjo about 12 years
    I didn't notice earlier, Your workaround also works great. Now finally i can do my own multithreaded python apps using GUI. Thanks for help.
  • Fabrizio
    Fabrizio almost 11 years
    There are really a lot of examples (maybe not for PyQT) about this but as the many many examples out there this example as well will allow to print the output of the thread only after the urllib2 call has ended. This is a big limitation in my opinion. Not that in the sense that it does not work but in the sense that a more realistic implementation would need data flow stuff (e.g. pipe), and this is more complicated.
  • Winand
    Winand about 9 years
    But it's forbidden to interact with gui directly from non-gui(main) thread, isn't it?
  • Aaron Digulla
    Aaron Digulla about 9 years
    Note: The first example is wrong. You're not allowed to call QListWidget.addItem() from a thread. You have to use signals.
  • three_pineapples
    three_pineapples almost 9 years
    Yep, this is a terrible answer (sorry). If you do this, your program will randomly crash. Qt widgets are not thread safe...
  • reclosedev
    reclosedev almost 9 years
    @three_pineapples, thanks for edit! I agree about widgets and thread safety. That code worked because of luck on specific version, I guess.
  • Josh Usre
    Josh Usre about 8 years
    How would I adapt this for multiple workers with multiple enumerate gui elements/