Ошибка многопоточности Pyqt5: QObject :: connect: невозможно поставить в очередь аргументы типа 'QTextCursor' - PullRequest
0 голосов
/ 02 августа 2020

Я работаю над проектом, который действует как приложение чата, и каждый раз, когда я открываю новый поток через gui pyqt5, появляется сообщение об ошибке: QObject :: connect: Невозможно поставить в очередь аргументы типа «QTextCursor». Я действительно не знаю, что делаю не так, и очень ценю вашу помощь. Заранее спасибо.

Вот мой код:

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QInputDialog
from PyQt5.QtWidgets import QPlainTextEdit
from socket import socket, AF_INET6
from socket import SOCK_STREAM, SOCK_DGRAM
from socket import gethostbyname, gethostname 
from threading import Thread
import sys


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        self.MainWindow = MainWindow.setObjectName("MainWindow")
        MainWindow.resize(251, 335)

        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")

        self.BigBox = QtWidgets.QPlainTextEdit(self.centralwidget)
        self.BigBox.setGeometry(QtCore.QRect(10, 10, 231, 211))
        self.BigBox.setObjectName("BigBox")

        self.SmallBox = QtWidgets.QPlainTextEdit(self.centralwidget)
        self.SmallBox.setGeometry(QtCore.QRect(10, 230, 231, 31))
        self.SmallBox.setObjectName("SmallBox")

        self.hostButton = QtWidgets.QPushButton(self.centralwidget)
        self.hostButton.setGeometry(QtCore.QRect(90, 270, 75, 23))
        self.hostButton.setObjectName("hostButton")

        self.submitButton = QtWidgets.QPushButton(self.centralwidget)
        self.submitButton.setGeometry(QtCore.QRect(170, 270, 75, 23))
        self.submitButton.setObjectName("submitButton")

        self.connectButton = QtWidgets.QPushButton(self.centralwidget)
        self.connectButton.setGeometry(QtCore.QRect(10, 270, 75, 23))
        self.connectButton.setObjectName("connectButton")
        MainWindow.setCentralWidget(self.centralwidget)


        self.connectButton.clicked.connect(self.popForConnect)
        self.hostButton.clicked.connect(self.popForHost)
        self.submitButton.clicked.connect(self.takeValue)

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

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Chat Room"))
        self.hostButton.setText(_translate("MainWindow", "Host"))
        self.submitButton.setText(_translate("MainWindow", "Submit"))
        self.connectButton.setText(_translate("MainWindow", "Connect"))

    def popForConnect(self):
        self.ip, x = QInputDialog.getText(self.MainWindow, "Connection Options", "Enter The IP: ")
        self.port2, y = QInputDialog.getInt(self.MainWindow, "Connection Options", "Enter The Port: ")
        self.mainConnect()

    def mainConnect(self):
        self.hs = socket(AF_INET6, SOCK_STREAM)
        self.IPAddr = gethostbyname(gethostname())

        getMsg = Thread(target=self.getMessages)
        getMsg.start()

        try:
            self.hs.connect((self.ip, int(self.port2), 0, 0))
            self.hs.send(bytes("[+] Connection Established", "utf8"))
            self.sendMessages()
        except (ConnectionRefusedError, TimeoutError):
            self.BigBox.appendPlainText("[!] Server Is Currently Full")

    def getMessages(self):
        self.js = socket(AF_INET6, SOCK_DGRAM)
        self.js.bind(("", int(self.port2+2), 0, 0))
        while True:
            msg2 = self.js.recvfrom(1024)
            formatedMsg = msg2[0].decode("utf8")
            if formatedMsg == f"[{self.IPAddr}]: [!] User Disconnected" and formatedMsg[1:13] == self.IPAddr:
                self.BigBox.appendPlainText(formatedMsg)
                self.BigBox.repaint()
                self.js.close()
                sys.exit()
            self.BigBox.appendPlainText(formatedMsg)
            self.BigBox.repaint()

    def sendMessages(self):
        while True:
            msg3 = self.takeValue()
            if msg3 == "quit" or msg3 == "exit":
                self.hs.send(bytes("[!] User Disconnected", "utf8"))
                break
            self.hs.send(bytes(msg3, "utf8"))
        self.hs.close()

    def popForHost(self):
        TEMPVAR = 0
        N_CONN = {}
        self.port, x = QInputDialog.getInt(self.MainWindow, "Host Options", "Enter The Port: ")
        self.mainHost(TEMPVAR, N_CONN)

    def mainHost(self, TEMPVAR, N_CONN):
        self.cs = socket(AF_INET6, SOCK_STREAM)

        self.vs = socket(AF_INET6, SOCK_DGRAM)
        self.vs.bind(("", int(self.port+1), 0, 0))

        self.cs.bind(("", int(self.port),0 ,0 ))
        self.BigBox.appendPlainText("[*] Listening on 0.0.0.0:"+str(self.port))
        self.BigBox.repaint()

        self.waitForConnections(TEMPVAR, N_CONN)

    def waitForConnections(self, TEMPVAR, N_CONN):
        for _ in range(2):
            self.cs.listen(1)
            self.conn, self.addr = self.cs.accept()

            self.BigBox.appendPlainText("[+] User Connected: "+str(self.addr[0])+ " Port: "+str(self.addr[1]))
            self.BigBox.repaint()
            N_CONN[self.addr[0]] = self.addr[1]

            prtMsg = Thread(target=self.printMessages, args=(TEMPVAR, N_CONN,))
            prtMsg.start()
            if TEMPVAR == 101:
                break
        TEMPVAR = 0

    def printMessages(self, TEMPVAR, N_CONN):
        while True:
            try:
                self.msg = self.conn.recv(1024).decode("utf8")
                if self.msg == "[!] User Disconnected":
                    self.BigBox.appendPlainText(self.formatedMsg(TEMPVAR, N_CONN))
                    self.BigBox.repaint()

                    N_CONN.pop(self.addr[0])
                    break
                self.BigBox.appendPlainText(self.formatedMsg(TEMPVAR, N_CONN))
                self.BigBox.repaint()
            except ConnectionResetError as e:
                self.BigBox.appendPlainText(f"[{self.addr[0]}]: [!] User Disconnected")
                self.BigBox.repaint()
                break
        self.conn.close()
        TEMPVAR = 101

    def formatedMsg(self, TEMPVAR, N_CONN):
        self.fmsg = f"[{self.addr[0]}]: "+self.msg

        for keys, values in N_CONN.items():
            self.vs.sendto(bytes(self.fmsg, "utf8"), (keys, int(self.port+2)))
        return self.fmsg


    def takeValue(self):
        return self.SmallBox.toPlainText()

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

Вы можете запустить программу, разместив ее на компьютере. [Нажмите Хост и введите желаемый порт]. Затем на другом компьютере вы можете выбрать подключение и ввести IP-адрес хост-машины и выбранный вами порт.

Ответы [ 2 ]

0 голосов
/ 03 августа 2020

Причина этой ошибки в том, что вы пытаетесь получить доступ к GUI элементам из другого потока, что должно никогда не выполняться.

Когда вы используете appendPlainText многое происходит «под капотом», включая изменения в базовом QTextDocument текстовом редакторе, который внутренне использует соединения сигналов / слотов для изменения макета документа и уведомления виджета об этом (это включает QTextCursor тоже). Как объяснялось ранее, Qt не может сделать это из отдельного потока.

Чтобы все это правильно сделать, вы должны использовать не базовый c python Thread, а QThread Вместо этого подкласс с настраиваемыми сигналами, к которым вы можете подключиться для обновления виджетов.

Я не могу переписать ваш пример, так как он слишком обширен, но я могу дать вам несколько советов по этому поводу.

Создание подклассов QThread для клиента и хоста

Это очень базовый c полу-псевдокод того, что может быть реализовано для класса хоста:

class Host(QtCore.QThread):
    newConnection = QtCore.pyqtSignal(object)
    messageReceived = QtCore.pyqtSignal(object)

    def __init__(self, port):
        super().__init__()
        self.port = port

    def run(self):
        while True:
            cs = socket(AF_INET6, SOCK_STREAM)
            cs.bind(("", int(self.port), 0, 0))
            conn, addr = self.cs.accept()
            self.newConnection.emit(addr)
            while True:
                self.messageReceived.emit(conn.recv(1024).decode("utf8"))

А затем, в основном классе, что-то вроде этого:

class MainWindow(QtWidgets.QMainWindow):
    def mainHost(self, port):
        self.socket = Host(port)
        self.socket.newConnection.connect(self.newConnection)
        self.socket.messageReceived.connect(self.BigBox.appendPlainText)
        self.socket.start()

    def newConnection(self, ip):
        self.BigBox.appendPlainText('New connection from {}'.format(ip)

НИКОГДА не используйте блокирующие функции / операторы в основном потоке

В вашем коде есть waitForConnections, который заблокирован до тех пор, пока self.cs.accept() возвращает; это препятствует правильному обновлению пользовательского интерфейса (например, при его перемещении или изменении размера) или получении какого-либо взаимодействия с пользователем, включая попытки закрыть окно.

Избегать repaint(), если это действительно не требуется

От документация :

Мы рекомендуем использовать repaint () только в том случае, если вам нужно немедленно перерисовать, например во время анимации. Практически во всех случаях лучше использовать update (), поскольку он позволяет Qt оптимизировать скорость и минимизировать мерцание.

Вообще говоря, вы должны использовать repaint(), только если вы знаете что и почему вы это делаете, и делать это из другого потока - не лучшая идея.

Не изменять pyuic сгенерированные файлы

Эти файлы предназначены для использования в качестве они есть, без каких-либо изменений (прочтите об этом , чтобы понять, как правильно использовать эти файлы. Обратите внимание, что вам не следует даже пытаться имитировать c их поведение. Если вы полностью создаете пользовательский интерфейс из кода, просто создайте подкласс QWidget, который вы используете (в вашем случае, QMainWindow).

Другие примечания:

  • не используйте сравнение строк для проверки состояний соединения / разъединения; подумайте, что произойдет, если я отправлю сообщение «[!] Пользователь отключен»;
  • избегайте ненужных функций: например, у вас есть mainHost и waitForConnections, которые фактически выполняются одна за другой; функции должны быть созданы для их повторного использования, если вы используете их только один раз, в них обычно нет реальной необходимости;
  • избегайте ненужных атрибутов экземпляра, если вы не собираетесь использовать их снова (например, self.fmsg используется в formatedMsg());
  • фиксированная геометрия редко бывает хорошей идеей, всегда лучше использовать менеджеры компоновки;
  • имена переменных (как и имена функций) не должны быть заглавными или прописными (что обычно используется только для констант), подробнее об этих аспектах читайте в Руководстве по стилю для Python Код ;
  • вы устанавливаете TEMPVAR в конце циклов for и while; так как это переменная local , делать это бессмысленно;
  • соединение submitButton ничего не делает;

Наконец, вместо использования python socket, вы также можете использовать выделенные классы Qt: QTcpSocket и QUdbSocket .

0 голосов
/ 03 августа 2020

Используйте QThread вместо Thread.

Кроме того, вы можете использовать QTcpSocket и выполнять супер-вызовы QAbstractSocket вместо использования встроенных модулей.

https://doc.qt.io/qt-5/qthread.html

https://doc.qt.io/qt-5/qtcpsocket.html

...