1
votes

I have a GUI made in Designer (pyqt5). A function in my main class needs to work on a separate thread. I also catch the stdout on a QtextEdit LIVE during operations. Everything so far works.

Right now I'm trying to implement a ProgressBar onto my main GUI form. The bar needs to show live progression just like it does on the textEdit.

The example code below works on Linux without any warnings. But on Windows I get the error:

QObject::setParent: Cannot set parent, new parent is in a different thread

I know that this is due to me having a ui element modification within my threaded function. I did my research but all the answers point to using QThreads (just when I started to understand basic threading!). I would prefer a way to update my GUI without having to change the current threading system below.

Here is the example code:

import sys
import threading
import time

from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QTextCursor

from ui_form import Ui_Form


class EmittingStream(QObject):
    textWritten = pyqtSignal(str)

    def write(self, text):
        self.textWritten.emit(str(text))


class Form(QMainWindow):
    finished = pyqtSignal()

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

        # Install the custom output stream
        sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)

        self.ui = Ui_Form()
        self.ui.setupUi(self)

        self.ui.pushButton_run.clicked.connect(self.start_task)
        self.finished.connect(self.end_task)

    def start_task(self):

        self.thread = threading.Thread(target=self.run_test)
        self.thread.start()
        self.ui.pushButton_run.setEnabled(False)

    def end_task(self):
        self.ui.pushButton_run.setEnabled(True)

    def __del__(self):
        # Restore sys.stdout
        sys.stdout = sys.__stdout__

    def normalOutputWritten(self, text):
        """Append text to the QTextEdit."""
        cursor = self.ui.textEdit.textCursor()
        cursor.movePosition(QTextCursor.End)
        cursor.insertText(text)
        self.ui.textEdit.setTextCursor(cursor)
        self.ui.textEdit.ensureCursorVisible()

    def run_test(self):
        for i in range(100):
            per = i + 1
            self.ui.progressBar.setValue(per)
            print("%%%s" % per)
            time.sleep(0.15)  # simulating expensive task

        print("Task Completed!")
        time.sleep(1.5)
        self.ui.progressBar.reset()
        self.finished.emit()


def main():
    app = QApplication(sys.argv)
    form = Form()
    form.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

the ui:

# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'form.ui'
#
# Created: Mon Apr 30 13:43:19 2018
#      by: PyQt5 UI code generator 5.2.1
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(Form)
        self.centralwidget.setObjectName("centralwidget")
        self.pushButton_run = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_run.setGeometry(QtCore.QRect(40, 20, 311, 191))
        self.pushButton_run.setObjectName("pushButton_run")
        self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
        self.textEdit.setGeometry(QtCore.QRect(40, 230, 721, 241))
        self.textEdit.setObjectName("textEdit")
        self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
        self.progressBar.setGeometry(QtCore.QRect(40, 490, 721, 23))
        self.progressBar.setObjectName("progressBar")
        Form.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(Form)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 25))
        self.menubar.setObjectName("menubar")
        Form.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(Form)
        self.statusbar.setObjectName("statusbar")
        Form.setStatusBar(self.statusbar)

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

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "MainWindow"))
        self.pushButton_run.setText(_translate("Form", "RUN"))

Somehow I need to -instantly- inform the gui thread (from my running thread) that the progress bar value is changing (a process that could take up minutes to complete).

1
If anyone could at least point me to the right direction.. Is it possible, to do what I want with the format above? Or do I have to use QThreads?shafuq

1 Answers

2
votes

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)
            ...