本文介绍了Pyqt5多线程错误:QObject::Connect:无法对类型'QTextCursor'的参数进行排队的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在处理一个类似聊天应用程序的项目,每次我通过pyqt5的图形用户界面打开一个新线程时,都会出现一个错误:QObject::Connect:Cannot Queue of‘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和您选择的端口。

推荐答案

出现该错误的原因是您正在尝试从另一个线程访问图形用户界面元素,这是绝不能执行的操作。

当您使用appendPlainText时,在幕后会发生许多事情,包括对文本编辑的底层QTextDocument的更改,它在内部使用信号/槽连接来更改文档布局并通知小部件(这也包括QTextCursor实例)。如前所述,Qt不能从单独的线程执行此操作。

为了正确完成所有这些操作,您不应该使用基本的pythonThread,而应该使用QThread子类,它带有您可以连接到以更新小部件的自定义信号。

我不能重写你的例子,因为它太广泛了,但我可以给你一些建议。

为客户端和主机创建QThread的子类

这是可以为HOST类实现的非常基本的半伪代码:

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

来自documentation

一般来说,只有在知道要做什么以及为什么要做的情况下才应该使用repaint(),而从另一个线程来做并不是一个好主意。

不修改pyuic生成的文件

这些文件应按原样使用,不做任何修改(请阅读更多about it以了解如何正确使用这些文件。
请注意,您甚至不应该尝试模仿他们的行为。如果您完全从代码构建UI,只需将您正在使用的QWidget子类化(在本例中为QMainWindow)。


其他备注:

  • 不要使用字符串比较来检查连接/断开状态;想想如果我发送一个[!]用户已断开连接;消息;
  • 避免不必要的函数:例如,您有一个又一个实际执行的mainHostwaitForConnections;创建函数应该是为了它们的可重用性,如果只使用一次,通常并不需要它们;
  • 如果不打算再次使用,请避免不必要的实例属性(例如,formatedMsg()中使用的self.fmsg);
  • 固定的几何图形很少是好主意,始终倾向于使用布局管理器;
  • 变量名(与函数名一样)不应大写,也不应大写(通常仅用于常量),请阅读Style Guide for Python Code
  • 中有关这些方面的更多信息
  • 您在For和While循环的末尾设置TEMPVAR;因为它是一个局部变量,所以这样做没有意义;
  • submitButton连接不执行任何操作;

最后,您还可以使用Qt专用类:QTcpSocketQUdbSocket,而不是使用Python的套接字。

这篇关于Pyqt5多线程错误:QObject::Connect:无法对类型'QTextCursor'的参数进行排队的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-20 21:52