Search code examples
pythonmultithreadingqthread

Python threading - Cannot set parent, new parent is in a different thread


I'm trying to make a GUI that, once you press a button a scrollArea would dynamically get updated. I tried using QThread to not lock the GUI and to show the user the scrollArea as it gets populated with elements. I made a thread worker class where inside a loop I'm creating the elements for the scrollArea.The elements are put on a QVBoxLayout and I'm trying to pass that layout to my GUI scrollArea using a signal of type QObject. I'm receiving this error: QObject::setParent: Cannot set parent, new parent is in a different thread I don't know what to do to solve this

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

class WorkerThread(QThread):
    
    my_signal = pyqtSignal(QObject)

    def run(self):
        top_layout = QtWidgets.QVBoxLayout()
        for i in range(2500):
            group_box = QtWidgets.QGroupBox()
            group_box.setTitle('box {0}'.format(i))
            label = QtWidgets.QLabel()
            label.setText('label {0}'.format(i))
            layout = QtWidgets.QHBoxLayout(group_box)
            layout.addWidget(label)
            top_layout.addWidget(group_box)
            self.my_signal.emit(top_layout)

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(640, 480)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(520, 30, 71, 51))
        self.pushButton.setObjectName("pushButton")
        self.scrollArea = QtWidgets.QScrollArea(self.centralwidget)
        self.scrollArea.setGeometry(QtCore.QRect(0, 90, 511, 461))
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setObjectName("scrollArea")
        self.scrollAreaWidgetContents = QtWidgets.QWidget()
        self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 505, 455))
        self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
        self.scrollArea.setWidget(self.scrollAreaWidgetContents)
        MainWindow.setCentralWidget(self.centralwidget)  
        self.pushButton.clicked.connect(self.click)
        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton.setText(_translate("MainWindow", "add"))




    def click(self):
        self.worker = WorkerThread()
        self.worker.start()
        self.worker.my_signal.connect(self.update_scrollArea)


    def update_scrollArea(self, top_layout):
        top_widget = QtWidgets.QWidget()
        top_widget.setLayout(top_layout)
        self.scrollArea.setWidget(top_widget)




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

Solution

  • I've had problems with creating PyQt5 objects in threads in the past, and one option for you would be to use a timer instead.

    class Ui_MainWindow(object):
        def __init__(self):
            self.timer = QtCore.QBasicTimer()
            self.timer.start(250, self)
            self.i = 0
            
        def timerEvent(self, event):
            if self.i < 2500:
                self.i+=1
                top_layout = QtWidgets.QVBoxLayout()
                group_box = QtWidgets.QGroupBox()
                group_box.setTitle('box {0}'.format(i))
                label = QtWidgets.QLabel()
                label.setText('label {0}'.format(i))
                layout = QtWidgets.QHBoxLayout(group_box)
                layout.addWidget(label)
                top_layout.addWidget(group_box)
                self.my_signal.emit(top_layout)
        
        def setupUi(self, MainWindow):
            ...
    

    This way you will interrupt the main loop once every 250msec to update one loop iteration at a time and you will not freeze up the GUI.