Search code examples
pythonqthreadpyside2

PySide2 : cannot close a QMessageBox without quitting the APP


Here is a little code, where I have a thread connecting to a ftp server and showing message boxes. I don't understant why, as soon as the msgBoxWait dialog is closed, the app crashed (terminates before the FTP thread).

I guess this is because it is the last window displayed, however, adding a QEventLoop doesn't fix it. Can you help me ?

# coding: utf-8

import sys
import random
from PySide2 import QtCore, QtWidgets, QtGui


class Logic():
    def __init__(self):
        self.msgBoxWait = QtWidgets.QMessageBox()
        self.ftpThread = FtpThread()
        self.isConnected = False
        self.loop = QtCore.QEventLoop()

    def run(self):
        self.ftpThread.sigIsConnected.connect(self.ftpConnected, QtCore.Qt.QueuedConnection)
        self.ftpThread.finished.connect(self.ftpFinished, QtCore.Qt.QueuedConnection)
        self.ftpThread.sigError.connect(self.ftpError, QtCore.Qt.QueuedConnection)
        self.ftpThread.start()

        QtCore.QTimer.singleShot(200, self.showWaitMsgBox)

        self.loop.exec_()

    def showWaitMsgBox(self):
        self.msgBoxWait.setWindowTitle("Waiting")
        self.msgBoxWait.setText("""Waiting for ftp connection""")
        if not self.isConnected:
            self.msgBoxWait.exec()

    def ftpConnected(self):
        print("connected")
        self.isConnected = True
        self.msgBoxWait.close()  # <- crash here or when I click on the close button

    def ftpFinished(self):
        print("finished")
        self.ftpThread = None
        self.loop.quit()

    def ftpError(self, title, message):
        QtWidgets.QMessageBox.critical(None, title, message)


class FtpThread(QtCore.QThread):
    sigIsConnected = QtCore.Signal()
    sigError = QtCore.Signal(str, str)

    def run(self):
        QtCore.QThread.sleep(2)
        self.sigIsConnected.emit()
        QtCore.QThread.sleep(1)
        self.sigError.emit("error", "An error appened")
        QtCore.QThread.sleep(3)


if __name__ == "__main__":
    app = QtWidgets.QApplication([])

    logic = Logic()
    QtCore.QTimer.singleShot(0, logic.run)

    sys.exit(app.exec_())

Solution

  • By default, Qt is configured so that if the last window is closed, the application will end because it is generally the expected behavior but that is not your case because there are times when you need to continue running even if there are no open windows. The solution is to set the quitOnLastWindowClosed property to False:

    # coding: utf-8
    
    import sys
    import random
    from PySide2 import QtCore, QtWidgets, QtGui
    
    
    class Logic:
        def __init__(self):
            self.msgBoxWait = QtWidgets.QMessageBox()
            self.ftpThread = FtpThread()
            self.isConnected = False
    
        def run(self):
            self.ftpThread.sigIsConnected.connect(
                self.ftpConnected, QtCore.Qt.QueuedConnection
            )
            self.ftpThread.finished.connect(self.ftpFinished, QtCore.Qt.QueuedConnection)
            self.ftpThread.sigError.connect(self.ftpError, QtCore.Qt.QueuedConnection)
            self.ftpThread.start()
    
            QtCore.QTimer.singleShot(200, self.showWaitMsgBox)
    
        def showWaitMsgBox(self):
            self.msgBoxWait.setWindowTitle("Waiting")
            self.msgBoxWait.setText("""Waiting for ftp connection""")
            if not self.isConnected:
                self.msgBoxWait.exec()
    
        def ftpConnected(self):
            print("connected")
            self.isConnected = True
            self.msgBoxWait.close()
    
        def ftpFinished(self):
            print("finished")
            self.ftpThread = None
            # QtCore.QCoreApplication.quit() should be used
            # to close the entire application if necessary
            QtCore.QCoreApplication.quit()
    
        def ftpError(self, title, message):
            QtWidgets.QMessageBox.critical(None, title, message)
    
    
    class FtpThread(QtCore.QThread):
        sigIsConnected = QtCore.Signal()
        sigError = QtCore.Signal(str, str)
    
        def run(self):
            QtCore.QThread.sleep(2)
            self.sigIsConnected.emit()
            QtCore.QThread.sleep(1)
            self.sigError.emit("error", "An error appened")
            QtCore.QThread.sleep(3)
    
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication([])
        app.setQuitOnLastWindowClosed(False)
    
        logic = Logic()
        QtCore.QTimer.singleShot(0, logic.run)
    
        sys.exit(app.exec_())