Proper way to handle the close button in a main window PyQt, (Red "X")

38,736

Solution 1

You shouldn't ever modify the class that was generated from your ui file. Instead you should subclass and modify the subclass.

From the looks of your code, you are actually creating two QMainWindows and the closeEvent is being caught for the wrong one (presumably that one is hidden?). That is self.ui is a QMainWindow that is not being shown, and is not added to the UI of GUIForm. Instead you are using the Ui_MainWindow.setupUi() method explicitly yourself, to add the widgets to your own QMainWindow, 'GUIForm`.

Instead, what you should to do is leave your Ui_MainWindow class as it was when it was generated from the ui file, and then modify your main python file to be:

class GUIForm(Ui_MainWindow):
    def __init__(self, parent=None):
        Ui_MainWindow.__init__(self, parent)
        self.threadData()

    def closeEvent(self, event):
        print "User has clicked the red x on the main window"
        event.accept()


if __name__ == "__main__":

    app = QtGui.QApplication(sys.argv)
    myapp = GUIForm()
    myapp.show()
    ret = app.exec_()
    sys.exit(ret)

This way you are extending the behaviour of the auto-generated UI file. This makes it easy to regenerate the python file from the .ui file without having to re-add code (this is precisely why you should never modify the auto-generated Python file)

Solution 2

Sometimes, issues occur when handling event signals from the main window.

You can use the code :

app.aboutToQuit.connect(self.closeEvent)

and you can write your own code in the closeEvent function.

Note :

app is the name of the QtGui.QApplication instance

Here's a demo of the full code :

from PyQt4 import QtCore, QtGui

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtGui.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        MainWindow.setCentralWidget(self.centralwidget)
        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

        #{================================

        app.aboutToQuit.connect(self.closeEvent)

        #}================================

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle('Demo')

    #{================================

    def closeEvent(self):
        #Your desired functionality here
        print('Close button pressed')
        import sys
        sys.exit(0)

    #}================================


if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    MainWindow = QtGui.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

Hope it helps.

Solution 3

I ran into this same problem, where I had a dialog window prompt, asking the user for input before proceeding in an operation. The user could add input before the operation was processed, skip adding input and process anyway or cancel out of the operation using a Cancel button or by clicking the X of the dialog window.

What I did instead was create a variable holding a boolean value that would be checked before closing the window, so that in the event the window was forced closed, it's clear that a process was aborted.

class Ui_MainWindow(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.setupUi(self)
        #This variable will be your way of determining how the window was closed
        self.force_close = True
        self.buttonOk.clicked.connect(self.someOtherFunction)

    def setupUi(self, MainWindow):
        #setup code goes here

    def retranslateUi(self, MainWindow):
        #re translation of the GUI code 

    def someOtherFunction(self):
        # Do some things here if needed
        print('All done')
        # Assuming some operation is performed and a value or result is generated            
        # Since this function completed, there is no need to force close the window
        # So it can be set to False
        self.force_close = False
        # the below close() is a built-in function and will automatically trigger the
        # closeEvent
        self.close()

    def closeEvent(self, event):            
        if self.force_close is True:
            # Here is where you could add you code to perform some operation
            # due to the user clicking the X
Share:
38,736
sudobangbang
Author by

sudobangbang

Updated on April 15, 2020

Comments

  • sudobangbang
    sudobangbang about 4 years

    First off, I am a complete newbie to PyQt.

    I have been trying to link a function to the Main Window's close button (the red x in the corner of the window) but I haven't been having any success. Right now, my code looks something like this:

    class Ui_MainWindow(QtGui.QMainWindow):
        def __init__(self):
            QtGui.QMainWindow.__init__(self)
            self.setupUi(self)
        def setupUi(self, MainWindow):
            #setup code goes here
        def retranslateUi(self, MainWindow):
            #re translation of the GUI code 
        def closeEvent(self, event):
            print "User has clicked the red x on the main window"
    

    In a seperate "main" file I have the folowing:

    class GUIForm(QtGui.QMainWindow):
        def __init__(self, parent=None):
            QtGui.QMainWindow.__init__(self,parent)
            self.ui = Ui_MainWindow()
            self.ui.setupUi(self)
            #self.ui.ECUStatus_txt = MyWidget.__init__.text_list
            self.threadData()
    
    
        if __name__ == "__main__":
    
            app = QtGui.QApplication(sys.argv)
            myapp = GUIForm()
            myapp.show()
            ret = app.exec_()
            sys.exit(ret)
    

    However, when I run via command prompt, I am not able to see the print statement when I hit the red x. I know I am new to Qt, but I have seen quite a few people ask this question, and none of the answers seemed to get beyond what is already written above.

    One of these answers: Answer #1 Answer #2

    Both of these solutions are similar to mine, but it still doesn't work

    Despite the answer, which may have worked for that user's specific code, my fellow PyQt colleagues and I are still quite foggy on the reasoning ours is not working. Is there a defined button name for the "Red X box" built into PyQt? Can I connect it to another function the someway I would for other buttons?

  • Kajsa
    Kajsa over 5 years
    The aboutToQuit signal is too late for me. I want to have a popup ask the user if they want to quit or not but once this signal is launched the app has already quit and I just get the question popup which now has no effect on if the app closes or not. Any ideas?
  • xeon
    xeon over 5 years
    The 'aboutToQuit' event invokes the other function closeEvent. You can place an if-else logic here to check for the confirmation. Ultimately 'sys.exit(0)' closes the application.
  • Kajsa
    Kajsa over 5 years
    that is how I tried to do it, but the gui closes before the confirmation popup is opened so even if you choose No the program has already been closed.
  • I.K.
    I.K. over 4 years
    Is there a way to bypass the issue stated by @Kajsa?
  • NL23codes
    NL23codes about 4 years
    I think the problem with this example is that it does not include any method for differentiating between the X closed and the window being closed due to an operation completing - say, clicking an OK button or something. Since closeEvent runs any time the window is closed and is not unique.
  • three_pineapples
    three_pineapples almost 4 years
    @NL23codes Most people don't need to make that distinction, and Qt doesn't have an explicit way to distinguish the "x" button because that button is handled by the OS window manager. However, based on this you can effectively distinguish it by looking at the value of event.spontaneous() inside the closeEvent method.
  • shailu
    shailu about 3 years
    this example works for me but closeEvent takes two arguments just give like that (self,g).