Как создать виджет GUI в неосновном потоке

#python #sockets #pyqt5

#python #сокеты #pyqt5

Вопрос:

У меня есть простая программа чата сервер-клиент, в которой я использую потоки для получения данных с сервера в фоновом режиме. Проблема в том, что когда клиент получает сообщение, необходимо создать новую вкладку для чата. Но я получаю данные в потоке, поэтому я могу проверять данные и создавать их в потоке. За исключением того, что я не могу создать виджет и установить его родительское окно my, потому что мое окно находится в основном потоке. Итак, это выглядит так:

 class MainWindow(QWidget):
    ...
        def addChatTab(self, nick, target, parent):
            tab = ChatTab(target)
            tab.setParent(parent)  #where I get the eror
            self.chatTabWidget.addTab(tab, nick)
            self.chatTabs[nick] = tab


class ServerManagement():
    ...
    def clientLoop(self): #runs in a different thread
        ...
        if sender == settingsManager.getUserNick():
            targetTab = receiver
        else:
            targetTab = sender

        if targetTab in mainWindow.chatTabs.keys():   #if tab is already there
            mainWindow.getChatTab(targetTab).write(message)
        else:
            mainWindow.addChatTab(targetTab, sender, mainWindow)     #create and add it to QTabWidget
            mainWindow.getChatTab(targetTab).write(message)
  

Ошибка:

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

Я понимаю, как и почему это происходит, но у меня просто нет решения для этого. Кто-нибудь может мне помочь?

Заранее спасибо…

Комментарии:

1. @eyllanesc поскольку мой код действительно запутан и неорганизован, потребовалась бы половина кода, чтобы воспроизвести проблему. Все, что мне нужно, это способ создать виджет и установить его родительский QWidget в другом потоке.

2. Вы не должны создавать GUI в другом потоке, что вам следует сделать, это отправить информацию в поток GUI с помощью сигналов

Ответ №1:

Вам не следует изменять (понимать также как создавать) графический интерфейс из другого потока, идея состоит в том, чтобы отправлять информацию из вторичного потока через сигналы, для этого мы можем заставить ServerManagement наследовать от QObject, чтобы он мог создавать сигналы и в общей области видимости между объектом ServerManagement и MainWindow устанавливать соединение:

 class MainWindow(QWidget):
    # ...
    def addChatTab(self, nick, target, parent):
        tab = ChatTab(target)
        tab.setParent(parent)  #where I get the eror
        self.chatTabWidget.addTab(tab, nick)
        self.chatTabs[nick] = tab

    def foo_function(self, another_args)
       # ... foo function is the method where you create the Server Management object
       self.management = ServerManagement()
       self.management.targetChanged.connect(self.update_gui)
       #
    @pyqtSlot(str)
    def update_gui(self, targetTab):
        if targetTab in self.chatTabs.keys():   #if tab is already there
            self.getChatTab(targetTab).write(message)
        else:
            self.addChatTab(targetTab, sender, self)     #create and add it to QTabWidget
            self.getChatTab(targetTab).write(message)

class ServerManagement(QObject):
    targetChanged = pyqtSignal(str)
    def __init__(self, others_arguments):
        super(ServerManagement, self).__init__()
        # ...

    def clientLoop(self): #runs in a different thread
        # ...
        if sender == settingsManager.getUserNick():
            targetTab = receiver
        else:
            targetTab = sender
        self.targetChanged.emit(targetTab)
  

Комментарии:

1. Я получаю ошибку AttributeError: 'PyQt5.QtCore.pyqtSignal' object has no attribute 'connect' при self.management.targetChanged.connect(self.update_gui)

2. @mbilal25tr Вы сделали ServerManagement наследуемым от QObject?

3. ДА. Я проверил еще раз, и у меня есть class ServerManagement(QtCore.QObject): и в init super().__init__()

4. @mbilal25tr Я думаю, что есть что-то, что вы не реализовали, у вас не должно возникнуть этой проблемы, поэтому вы должны предоставить MCVE

5. Я погуглил ошибку, и перемещение self.management.targetChanged.connect(self.update_gui) за пределы инициализации сработало. Мне пришлось всего лишь исправить пару ошибок в моем собственном коде. Спасибо!