Pyqt5 多线程错误:QObject::connect: 无法对“QTextCursor”类型的参数进行排队
Posted
技术标签:
【中文标题】Pyqt5 多线程错误:QObject::connect: 无法对“QTextCursor”类型的参数进行排队【英文标题】:Pyqt5 Multi-threading Error: QObject::connect: Cannot queue arguments of type 'QTextCursor' 【发布时间】:2020-08-02 20:11:41 【问题描述】:我正在开发一个类似于聊天应用程序的项目,每次我通过 pyqt5 的 gui 打开一个新线程时,都会出现错误说明:QObject::connect: Cannot queue arguments of type '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 和您选择的端口。
【问题讨论】:
【参考方案1】:该错误的原因是您试图从另一个线程访问 GUI 元素,这是必须永远做的事情。
当您使用appendPlainText
时,“幕后”会发生很多事情,包括对文本编辑的底层QTextDocument 的更改,它在内部使用信号/插槽连接来更改文档布局并通知小部件(这也包括QTextCursor 实例)。如前所述,Qt 无法从单独的线程中执行此操作。
为了正确执行所有这些操作,您不应使用基本的 python Thread
,而应使用 QThread 子类,并使用可以连接到更新小部件的自定义信号。
我不能重写你的例子,因为它太广泛了,但我可以给你一些建议。
为客户端和主机创建 QThread 的子类
这是可以为主机类实现的非常基本的半伪代码:
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()
返回;这会阻止 UI 正确更新(例如,在移动或调整其大小时)或接收任何用户交互,包括尝试关闭窗口。
除非真的需要,否则避免使用repaint()
来自documentation:
我们建议仅在需要立即重新绘制时才使用 repaint(),例如在动画期间。在几乎所有情况下 update() 都更好,因为它允许 Qt 优化速度并最大限度地减少闪烁。
一般来说,只有当您知道您在做什么以及为什么要这样做时,您才应该使用repaint()
,并且从另一个线程执行它不是一个好主意。
不要修改pyuic
生成的文件
这些文件旨在按原样使用,无需任何修改(阅读更多 about it 以了解如何正确使用这些文件。 请注意,您甚至不应该试图模仿他们的行为。如果您完全从代码构建 UI,只需子类化您正在使用的 QWidget(在您的情况下为 QMainWindow)。
其他说明:
不要使用字符串比较来检查连接/断开状态;想想如果我发送“[!] User Disconnected”消息会发生什么; 避免不必要的函数:例如,你有mainHost
和waitForConnections
,它们实际上是一个接一个地执行的;应该为它们的可重用性而创建函数,如果你只使用它们一次,通常不需要它们;
如果您不打算再次使用它们,请避免使用不必要的实例属性(例如,self.fmsg
在formatedMsg()
中使用);
固定的几何图形很少是个好主意,总是更喜欢使用布局管理器;
变量名称(与函数名称一样)不应大写,也不应大写(通常仅用于常量),请在Style Guide for Python Code 中阅读有关这些方面的更多信息;
您在 for 和 while 循环结束时设置了 TEMPVAR;因为它是一个本地变量,这样做是没有意义的;
submitButton
连接什么都不做;
最后,除了使用python的socket,你还可以使用Qt的专用类:QTcpSocket和QUdbSocket。
【讨论】:
【参考方案2】:使用 QThread 而不是线程。
此外,您可能希望使用 QTcpSocket 并对 QAbstractSocket 进行超级调用,而不是使用内置函数。
https://doc.qt.io/qt-5/qthread.html
https://doc.qt.io/qt-5/qtcpsocket.html
【讨论】:
以上是关于Pyqt5 多线程错误:QObject::connect: 无法对“QTextCursor”类型的参数进行排队的主要内容,如果未能解决你的问题,请参考以下文章