2

I followed this example (PyQT4) to create a "custom widget" in PyQT5 and ended up with following code:

progress.py

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import (QApplication,QMainWindow)

class Ui_Form(QMainWindow):
    def __init__(self, name, parent=None):
        super(Ui_Form,self).__init__(parent)
        #Form.setObjectName("Form")
        self.resize(619, 202)
        self.formLayoutWidget = QtWidgets.QWidget()
        self.formLayoutWidget.setGeometry(QtCore.QRect(0, 0, 621, 201))
        self.formLayoutWidget.setObjectName("formLayoutWidget")
        self.formLayout = QtWidgets.QFormLayout(self.formLayoutWidget)
        self.formLayout.setContentsMargins(0, 0, 0, 0)
        self.formLayout.setObjectName("formLayout")
        self.progressBar = QtWidgets.QProgressBar(self.formLayoutWidget)
        self.progressBar.setProperty("value", 24)
        self.progressBar.setObjectName("progressBar")
        self.formLayout.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.progressBar)
        self.graphicsView = QtWidgets.QGraphicsView(self.formLayoutWidget)
        self.graphicsView.setObjectName("graphicsView")
        self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.graphicsView)
        self.label_Title = QtWidgets.QLabel(self.formLayoutWidget)
        font = QtGui.QFont()
        font.setFamily("Verdana")
        font.setPointSize(22)
        font.setBold(True)
        font.setWeight(75)
        self.label_Title.setFont(font)
        self.label_Title.setObjectName("label_Title")
        self.formLayout.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.label_Title)
        self.timer = QtCore.QBasicTimer()
        self.step = 0
        self.timer.start(100,self)

        self.retranslateUi()
        QtCore.QMetaObject.connectSlotsByName(self)


        def timerEvent(self, e):
            if self.step>= 100:
                self.timer.stop()
                return
            self.step = self.step + 1
            self.progressBar.setValue(self.step)   

    def retranslateUi(self):
        _translate = QtCore.QCoreApplication.translate
        self.setWindowTitle(_translate("Form", "Form"))
        self.label_Title.setText(_translate("Form", "TextLabel"))

new_ui.py

import sys
from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(742, 538)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.pushButton_Start = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_Start.setGeometry(QtCore.QRect(630, 20, 91, 31))
        self.pushButton_Start.setObjectName("pushButton_Start")
        self.lineEdit_URL = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit_URL.setGeometry(QtCore.QRect(20, 20, 601, 31))
        self.lineEdit_URL.setObjectName("lineEdit_URL")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(20, 460, 46, 13))
        self.label.setObjectName("label")
        self.label_status = QtWidgets.QLabel(self.centralwidget)
        self.label_status.setGeometry(QtCore.QRect(73, 460, 651, 16))
        self.label_status.setObjectName("label_status")
        self.listWidget = QtWidgets.QListWidget(self.centralwidget)
        self.listWidget.setGeometry(QtCore.QRect(20, 60, 701, 391))
        self.listWidget.setObjectName("listWidget")
        #MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 742, 21))
        self.menubar.setObjectName("menubar")
        #MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        #MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)


    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "My Downloader"))
        self.pushButton_Start.setText(_translate("MainWindow", "Start"))
        self.label.setText(_translate("MainWindow", "Status :"))
        self.label_status.setText(_translate("MainWindow", "Ideal..."))




if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

main.py

import sys
import threading
import logging
from PyQt5 import QtCore, QtGui, QtWidgets
import json
import urllib.request
from new_ui import Ui_MainWindow
import progress
import random

class MyForm(QtWidgets.QDialog):
  def __init__(self, parent=None):
    super(MyForm, self).__init__(parent)
    self.ui = Ui_MainWindow()
    self.ui.setupUi(self)
    self.ui.pushButton_Start.clicked.connect(self.thread_start)

  def thread_start(self):
    p = threading.Thread(name='worker', target=self.start_download)
    #Do UI updates
    self.ui.label_status.setText('Fetching information...')
    #self.listWidget.addItem(prgstr)
    p.start()

  def start_download(self):

        ............
        code to fetch information from web
        ............

        #itm = self.ui.listWidget.addItem(json_data['entries'][0]['title'])
        self.ui.label_status.setText(json_data['entries'][0]['title'])
        item = QtWidgets.QListWidgetItem(self.ui.listWidget)
        item_widget = progress.Ui_Form("It works")
        item.setSizeHint(item_widget.sizeHint())
        self.ui.listWidget.addItem(item)
        self.ui.listWidget.setItemWidget(item,item_widget)


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    myapp = MyForm()
    myapp.show()
    sys.exit(app.exec_())

The above runs without any errors, but the following code fails to add any list item (custom widget), the label_status gets correctly populated with retrieved data - what am I missing here?

    self.ui.label_status.setText(json_data['entries'][0]['title'])
    item = QtWidgets.QListWidgetItem(self.ui.listWidget)
    item_widget = progress.Ui_Form("It works")
    item.setSizeHint(item_widget.sizeHint())
    self.ui.listWidget.addItem(item)
    self.ui.listWidget.setItemWidget(item,item_widget)

1 Answer 1

2

You should only ever update GUI elements from within the main GUI thread.

The worker thread should emit a signal when it's done, or perhaps do so periodically so that pending GUI events can be processed (i.e. by calling qApp.processEvents()).

If the worker thread generates data, use a Queue so that it can be accessed safely from the main thread.

UPDATE:

Here is a basic example (python3 only) of how to use QThread with a worker object. All communication between the GUI thread and the worker thread is done using signals and slots (see "Signals and Slots Across Threads" in the Qt docs for more on this).

import urllib.request
from PyQt4 import QtCore, QtGui

class Worker(QtCore.QObject):
    finished = QtCore.pyqtSignal()
    error = QtCore.pyqtSignal(object)
    dataReady = QtCore.pyqtSignal(object)

    @QtCore.pyqtSlot(str)
    def process(self, url):
        try:
            response = urllib.request.urlopen(url, timeout=20)
            try:
                self.dataReady.emit(response.read())
            finally:
                response.close()
        except urllib.error.URLError as exception:
            self.error.emit(exception.reason)
        except BaseException as exception:
            self.error.emit(str(exception))
        finally:
            self.finished.emit()

class Window(QtGui.QWidget):
    downloadRequest = QtCore.pyqtSignal(str)

    def __init__(self):
        QtGui.QWidget.__init__(self)
        self.viewer = QtGui.QPlainTextEdit(self)
        self.edit = QtGui.QLineEdit(self)
        self.edit.setFocus()
        self.button = QtGui.QPushButton('Download', self)
        self.button.clicked.connect(self.handleButton)
        layout = QtGui.QVBoxLayout(self)
        layout.addWidget(self.viewer)
        layout.addWidget(self.edit)
        layout.addWidget(self.button)
        # worker must not have a parent
        self._worker = Worker()
        self._thread = QtCore.QThread(self)
        self._worker.moveToThread(self._thread)
        self._worker.finished.connect(self._thread.quit)
        self._worker.error.connect(self.handleError)
        self._worker.dataReady.connect(self.handleDataReady)
        self.downloadRequest.connect(self._worker.process)

    def handleButton(self):
        url = self.edit.text().strip()
        if url and not self._thread.isRunning():
            self.viewer.clear()
            self._thread.start()
            # safely communicate with worker via signal
            self.downloadRequest.emit(url)

    def handleError(self, reason):
        self.viewer.clear()
        print('ERROR:', reason)

    def handleDataReady(self, data):
        self.viewer.setPlainText(data.decode('utf-8'))

    def closeEvent(self, event):
        if self._thread.isRunning():
            # Qt will complain if thread is still
            # running, so quit it before closing
            self._thread.quit()
            self._thread.wait()
        QtGui.QWidget.closeEvent(self, event)

if __name__ == '__main__':

    import sys
    app = QtGui.QApplication(sys.argv)
    window = Window()
    window.edit.setText('http://stackoverflow.com')
    window.setGeometry(500, 300, 300, 300)
    window.show()
    sys.exit(app.exec_())
Sign up to request clarification or add additional context in comments.

5 Comments

So you are saying that the reason there is no item_widget being populated here is because of using this approach. I am still learning things here with python and qt, can you pls. provide an example?
@Stacked. I have added an example to my answer. It's pretty self-explanatory, and you should be able to adapt it to your needs quite easily.
Thanks, will check and post back. Pls. tell how to retrieve download progress updates using the above method?
@Stacked. That is a separate issue from multithreading, so please ask a new question.
The problem is "how to track progress_hook updates from the downloader class in multithreading", pls. provide ideas and pointers if updating example is not possible.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.