Search code examples
pythonmultithreadingsocketspyqtpyqt6

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


I wrote an echo server with sockets In this way, the user clicks on join and connects to the server

Then he clicks on the send button to send and receive a text which is a string with 'message' value

Everything works fine

When the user clicks on the join button, he is correctly connected to the server

When he clicks on the send button, the message is correctly sent to the server, and it is correctly echoed from the server and comes to the client

And I can easily print the message in add_message function

The problem is that when I want to create a label from the message and put it in self.contentWidgetLayout in add_message function, I get this error ->

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

client.py


    from PyQt6.QtWidgets import *
    from PyQt6.QtGui import *
    from PyQt6.QtCore import *
    import socket
    import threading
    
    
    
    
    class Window(QMainWindow):
        def __init__(self):
            super().__init__()
            self.resize(600,600)
            self.show()
    
            #central widget
            centWidget = QWidget()
            self.centWidgetLayout = QVBoxLayout()
            self.centWidgetLayout.setSpacing(0)
            self.centWidgetLayout.setContentsMargins(0,0,0,0)
            centWidget.setLayout(self.centWidgetLayout)
            self.setCentralWidget(centWidget)
    
    
            #join
            self.join = QPushButton('join')
            self.join.clicked.connect(self.connect)
            self.centWidgetLayout.addWidget(self.join)
     
    
    
            #message box
            self.contentWidget = QWidget()
            self.contentWidgetLayout = QVBoxLayout()
            self.contentWidget.setLayout(self.contentWidgetLayout)
            self.centWidgetLayout.addWidget(self.contentWidget)
            self.contentWidget.setFixedHeight(330)
            
            
            #send message
            self.button = QPushButton('send')
            self.centWidgetLayout.addWidget(self.button)
            self.button.clicked.connect(self.send_message)
    
    
    
        def add_message(self,message):
            print(message)
            self.msg = QLabel(message)
            self.contentWidgetLayout.addWidget(self.msg)
            
    
    
    
        def connect(self):
            self.HOST = '127.0.0.1'
            self.PORT = 1234
            self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            
            try:
                self.client.connect((self.HOST, self.PORT))
                self.add_message("[SERVER] Successfully connected to the server")
            except:
                print('Error')
    
            username = 'john'
            if username != '':
                self.client.sendall(username.encode())
            else:
                print('Error')
    
            threading.Thread(target=self.listen_for_messages_from_server, args=(self.client, )).start()
    
    
    
        def send_message(self):
            message = 'message'
            if message != '':
                self.client.sendall(message.encode())
            else:
                print('error')
    
    
    
        def listen_for_messages_from_server(self,client):
            
            while True:
                message = client.recv(2048).decode('utf-8')
                if message != '':
                    self.add_message(message)
                    
                else:
                    print('Message recevied from client is empty')    
    
    app = QApplication([])
    win = Window()
    app.exec()


Solution

  • Qt widgets are not threadsafe. Qt even requires that all GUI operations run in the same thread.

    Qt uses the concept of thread affinity for its objects. This means that, e.g., a QLabel created in thread 1 will "belong" to thread 1. In your case, the QLabel is created in the listening thread and it belongs there. It cannot be added as a child to any GUI element belonging to the main thread.

    The resolution of your problem is to never interact with the GUI directly from the listening thread. Instead, you can emit a signal from listen_for_messages_from_server() whenever you receive a message. This signal can be connected to a slot which creates and adds the QLabel.

    To do so, ideally you have a helper object dealing with communication that derives from QObject and defines the signals. This object can then be created in the listening thread, or moved to it. Using QThread might help as well.