Pyqt5 Multi-threading Error: QObject::connect: Cannot queue arguments of type 'QTextCursor'
Pyqt5 Multi-threading Error: QObject::connect: Cannot queue arguments of type 'QTextCursor'
我正在开发一个像聊天应用程序一样的项目,每次我通过 pyqt5 的 gui 打开一个新线程时,都会出现一条错误消息:QObject::connect:无法将 [=17= 类型的参数排队].我真的不知道我做错了什么,非常感谢您的帮助。提前致谢。
这是我的代码:
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_())
您可以通过将程序托管在计算机上来启动它。[按主机并输入所需的端口]。接下来,在另一台计算机上,您可以 select 连接并输入主机的 IP 和您选择的端口。
使用 QThread 而不是线程。
此外,您可能希望使用 QTcpSocket 并对 QAbstractSocket 进行超级调用,而不是使用内置函数。
该错误的原因是您正试图从另一个线程访问 GUI 个元素,这是必须 永远 完成的事情。
当您使用 appendPlainText
时,“幕后”会发生很多事情,包括对基础 QTextDocument of the text edit, which internally uses signal/slot connections to alter the document layout and notify the widget about it (this includes QTextCursor 实例的更改)。如前所述,Qt 不能从单独的线程中执行此操作。
为了正确完成所有这些操作,您不应使用基本的 python Thread
,而应使用 QThread subclass,并使用自定义信号您可以连接到以更新小部件。
我不能重写你的例子,因为它太广泛了,但我可以给你一些建议。
为客户端和主机创建 QThread 的子classes
这是可以为主机实现的非常基本的半伪代码 class:
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 中,是这样的:
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)
切勿在主线程中使用阻塞 functions/statements
在您的代码中有 waitForConnections
,它在 self.cs.accept()
returns 之前被阻止;这会阻止 UI 正确更新(例如,在移动或调整其大小时)或接收任何用户交互,包括尝试关闭 window.
避免repaint()
除非真的需要
We suggest only using repaint() if you need an immediate repaint, for example during animation. In almost all circumstances update() is better, as it permits Qt to optimize for speed and minimize flicker.
一般来说,只有当您知道您在做什么以及为什么这样做时,您才应该使用repaint()
,并且从另一个线程来做并不是一件好事想法。
不要修改 pyuic
生成的文件
这些文件旨在按原样使用,无需任何修改(阅读更多内容about it以了解如何正确使用这些文件。
请注意,您甚至不应该尝试模仿他们的行为。如果您完全从代码构建 UI,只需将 class 您正在使用的 QWidget(在您的情况下为 QMainWindow)。
其他说明:
- 不要使用字符串比较来检查 connection/disconnection 状态;想想如果我发送一条“[!] User Disconnected”消息会发生什么;
- 避免不必要的功能:例如,您有
mainHost
和waitForConnections
实际上是一个接一个地执行;应该为它们的可重用性而创建函数,如果你只使用它们一次,通常没有真正需要它们;
- 如果您不打算再次使用它们,请避免使用不必要的实例属性(例如,
self.fmsg
在 formatedMsg()
中使用);
- 固定几何形状很少是个好主意,总是更喜欢使用布局管理器;
- 变量名称(与函数名称一样)不应大写,也不应大写(通常仅用于常量),请在 Style Guide for Python Code;
中阅读有关这些方面的更多信息
- 您在 for 和 while 循环结束时设置了 TEMPVAR;因为它是一个 local 变量,所以这样做是没有意义的;
submitButton
连接什么都不做;
最后,除了使用 python 的套接字,您还可以使用 Qt 专用的 classes:QTcpSocket and QUdbSocket.
我正在开发一个像聊天应用程序一样的项目,每次我通过 pyqt5 的 gui 打开一个新线程时,都会出现一条错误消息:QObject::connect:无法将 [=17= 类型的参数排队].我真的不知道我做错了什么,非常感谢您的帮助。提前致谢。
这是我的代码:
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_())
您可以通过将程序托管在计算机上来启动它。[按主机并输入所需的端口]。接下来,在另一台计算机上,您可以 select 连接并输入主机的 IP 和您选择的端口。
使用 QThread 而不是线程。
此外,您可能希望使用 QTcpSocket 并对 QAbstractSocket 进行超级调用,而不是使用内置函数。
该错误的原因是您正试图从另一个线程访问 GUI 个元素,这是必须 永远 完成的事情。
当您使用 appendPlainText
时,“幕后”会发生很多事情,包括对基础 QTextDocument of the text edit, which internally uses signal/slot connections to alter the document layout and notify the widget about it (this includes QTextCursor 实例的更改)。如前所述,Qt 不能从单独的线程中执行此操作。
为了正确完成所有这些操作,您不应使用基本的 python Thread
,而应使用 QThread subclass,并使用自定义信号您可以连接到以更新小部件。
我不能重写你的例子,因为它太广泛了,但我可以给你一些建议。
为客户端和主机创建 QThread 的子classes
这是可以为主机实现的非常基本的半伪代码 class:
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 中,是这样的:
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)
切勿在主线程中使用阻塞 functions/statements
在您的代码中有 waitForConnections
,它在 self.cs.accept()
returns 之前被阻止;这会阻止 UI 正确更新(例如,在移动或调整其大小时)或接收任何用户交互,包括尝试关闭 window.
避免repaint()
除非真的需要
We suggest only using repaint() if you need an immediate repaint, for example during animation. In almost all circumstances update() is better, as it permits Qt to optimize for speed and minimize flicker.
一般来说,只有当您知道您在做什么以及为什么这样做时,您才应该使用repaint()
,并且从另一个线程来做并不是一件好事想法。
不要修改 pyuic
生成的文件
这些文件旨在按原样使用,无需任何修改(阅读更多内容about it以了解如何正确使用这些文件。
请注意,您甚至不应该尝试模仿他们的行为。如果您完全从代码构建 UI,只需将 class 您正在使用的 QWidget(在您的情况下为 QMainWindow)。
其他说明:
- 不要使用字符串比较来检查 connection/disconnection 状态;想想如果我发送一条“[!] User Disconnected”消息会发生什么;
- 避免不必要的功能:例如,您有
mainHost
和waitForConnections
实际上是一个接一个地执行;应该为它们的可重用性而创建函数,如果你只使用它们一次,通常没有真正需要它们; - 如果您不打算再次使用它们,请避免使用不必要的实例属性(例如,
self.fmsg
在formatedMsg()
中使用); - 固定几何形状很少是个好主意,总是更喜欢使用布局管理器;
- 变量名称(与函数名称一样)不应大写,也不应大写(通常仅用于常量),请在 Style Guide for Python Code; 中阅读有关这些方面的更多信息
- 您在 for 和 while 循环结束时设置了 TEMPVAR;因为它是一个 local 变量,所以这样做是没有意义的;
submitButton
连接什么都不做;
最后,除了使用 python 的套接字,您还可以使用 Qt 专用的 classes:QTcpSocket and QUdbSocket.