Süleyman Yaman
Süleyman Yaman

Reputation: 241

PyQt Messagebox is crashing

I'm working on a file transfer application including its server. When I try to send a file between two clients, I want to ensure that the receiving client will get a message box like "x user wants to send a file. Do you accept?". I accomplished this so far but when I clicked the "Yes" button for testing purposes, the Yes button disappear and the receiving client collapses. When I tried to view the error on console, I saw that it's "QObject::setParent: Cannot set parent, new parent is in a different thread" on the receiving client. I searched the error on the site but couldn't make sense out of the solutions. Could you explain to me how I can solve this?

Code:

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
import socket
import json
import base64
from threading import Thread
from time import sleep

username = "admin"
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("144.122.86.204",5000)) #Fill here later
s.send(username.encode("utf-8"))

app = QApplication(sys.argv)
window = QWidget()
window.setWindowTitle("File Sharing")
window.setGeometry(0,0,500,350)

contactlist= QListWidget(window)
contactlist.setGeometry(5,5,100,250)


def add():
    user, ok = QInputDialog.getText(window, "Add a contact","Enter a 
username:")
    if ok:
        contactlist.addItem(str(user))

def send():
    target_user=contactlist.currentItem().text()
    name = QFileDialog.getOpenFileName(window, 'Open File')
    file = open(name[0], 'rb')
    base64_bytes = base64.b64encode(file.read())
    base64_string = base64_bytes.decode('utf-8')
    data= {"FRM": username, "TO": target_user, "DATA": base64_string}
    encoded_data = json.dumps(data).encode()
    s.sendall(encoded_data)

def receive():
    while True:
        data = s.recv(1024)
        if data:
            if 'wants to send you a file. Do you accept that?' in 
data.decode('utf-8'):
                choice = QMessageBox.question(window, 'FileTransfer', 
data.decode('utf-8'),QMessageBox. Yes|QMessageBox. No)
                if choice == QMessageBox.Yes:
                    s.send("CONFIRMED".encode('utf-8'))




t = Thread(target=receive)
t.start()

add_contact = QPushButton("Add a Contact", window)
add_contact.setGeometry(5,270,100,50)
add_contact.clicked.connect(add)

send_file = QPushButton("Send a File",window)
send_file.setGeometry(110,270,200,50)
send_file.clicked.connect(send)



window.show()

sys.exit(app.exec())

Upvotes: 2

Views: 1603

Answers (1)

eyllanesc
eyllanesc

Reputation: 244132

The problem in your case is that QMessageBox is being created in another thread which Qt prohibits, and its parent window lives in the main thread what Qt also forbids. The general approach in this case is to send the information of the secondary thread to the main thread through signals, events, metaobjects, etc. that are thread-safe and in the main thread is to create the GUI (in your case QMessageBox).

In this case it can be complicated so instead of using the above it is best to use the QtNetwork module of Qt that will send you the information received through signals making the while loop unnecessary and therefore the use of threads.

import json
import base64

from PyQt5 import QtCore, QtGui, QtWidgets, QtNetwork


class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Widget, self).__init__(parent)
        self.initUi()

        self.m_username = "admin"

        self.m_socket = QtNetwork.QTcpSocket(self)

        self.m_socket.connected.connect(self.onConnected)
        self.m_socket.readyRead.connect(self.onReadyRead)

        self.m_socket.connectToHost("144.122.86.204", 5000)

    @QtCore.pyqtSlot()
    def onConnected(self):
        username = "admin"
        self.m_socket.write(self.m_username.encode("utf-8"))
        self.m_socket.flush()

    @QtCore.pyqtSlot()
    def onReadyRead(self):
        data = self.m_socket.readAll().data()
        text = data.decode("utf-8")
        if "wants to send you a file. Do you accept that?" in text:
            choice = QtWidgets.QMessageBox.question(
                self,
                "FileTransfer",
                text,
                QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
            )
            if choice == QtWidgets.QMessageBox.Yes:
                self.m_socket.write("CONFIRMED".encode("utf-8"))

    @QtCore.pyqtSlot()
    def add_contant(self):
        user, ok = QtWidgets.QInputDialog.getText(
            self, "Add a contact", "Enter a username:"
        )
        if ok:
            self.m_listwidget.addItem(user)

    @QtCore.pyqtSlot()
    def send_file(self):
        if self.m_socket.state() != QtNetwork.QAbstractSocket.ConnectedState:
            print("Socket not connected")
            return

        item = self.m_listwidget.currentItem()
        if item is None:
            print("Not current item")
            return

        target_user = item.text()
        filename, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open File")
        with open(filename, "rb") as file:
            base64_bytes = base64.b64encode(file.read())
            base64_string = base64_bytes.decode("utf-8")
            data = {"FRM": self.m_username, "TO": target_user, "DATA": base64_string}
            encoded_data = json.dumps(data).encode()
            self.m_socket.write(encoded_data)
            self.m_socket.flush()

    def initUi(self):
        self.m_listwidget = QtWidgets.QListWidget()
        self.m_listwidget.setFixedWidth(100)

        self.m_add_button = QtWidgets.QPushButton("Add a Contact")
        self.m_add_button.clicked.connect(self.add_contant)
        self.m_add_button.setFixedSize(100, 50)
        self.m_send_file = QtWidgets.QPushButton("Send a File")
        self.m_send_file.clicked.connect(self.send_file)
        self.m_send_file.setFixedSize(200, 50)

        hlay = QtWidgets.QHBoxLayout()
        hlay.addWidget(self.m_listwidget)
        hlay.addStretch()

        hlay2 = QtWidgets.QHBoxLayout()
        hlay2.addWidget(self.m_add_button)
        hlay2.addWidget(self.m_send_file)
        hlay2.addStretch()

        vlay = QtWidgets.QVBoxLayout(self)
        vlay.addLayout(hlay)
        vlay.addLayout(hlay2)

        self.resize(500, 350)

    def closeEvent(self, event):
        if self.m_socket.state() == QtNetwork.QAbstractSocket.ConnectedState:
            self.m_socket.disconnectFromHost()
        super(Widget, self).closeEvent(event)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec())

Upvotes: 3

Related Questions