I am working on a project which acts like a chat application and everytime I open a new thread through the pyqt5's gui, there is an error stating: QObject::connect: Cannot queue arguments of type 'QTextCursor'. I don't really know what I am doing wrong and your help is greatly appreaciated. Thanks in advance.
Here is my code:
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_())
You can start the program by hosting it in a computer.[Press Host and enter the desired port]. Next, on the other computer you can select connect and type in the IP of the host machine and your chosen port.
The reason for that error is that you're trying to access GUI elements from another thread, which is something that must never be done.
When you use appendPlainText
, a lots happens "under the hood", including changes to the underlying 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 instances too). As explained before, Qt cannot do that from a separate thread.
In order to correctly do all of that, you should not use a basic python Thread
, but a QThread subclass instead, with custom signals that you can connect to to update the widgets.
I cannot rewrite your example, as it's too extensive, but I can give you some advice about it.
This is a very basic semi-pseudo-code of what could be implemented for the host 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"))
And then, in the main class, something like this:
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)
In your code you have waitForConnections
, which is blocked until self.cs.accept()
returns; this prevents the UI to correctly update (for example, while moving or resizing it) or receive any user interaction, including trying to close the window.
repaint()
unless really neededFrom the documentation:
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.
Generally speaking, you should only use repaint()
only if you know what and why you're doing it, and doing it from another thread is not a good idea.
pyuic
generated filesThose files are intended to be used as they are, without any modification (read more about it to understand how to correctly use those files.
Note that you should not even try to mimic their behavior. If you're completely building the UI from code, just subclass the QWidget you're using (in your case, QMainWindow).
Other notes:
mainHost
and waitForConnections
that are actually executed one after the other; functions should be created for their reusability, if you only use them once, there's usually no real need for them;self.fmsg
used in formatedMsg()
);submitButton
connection does nothing;Finally, instead of using python's socket, you can also use the Qt dedicated classes: QTcpSocket and QUdbSocket.