Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

children widget's closeEvent is not getting called on close

Tags:

python

pyside

Either I don't understand completely how Qt's event propagation works or something, but I cannot understand why exactly closeEvent is not being called both for QPushButton-derived class and for QWidget-derived one itself.

Shouldn't wid.closeEvent() trigger closeEvents of all children widgets?

#!/bin/env python
# -*- coding: utf-8 -*-
import sys, os
from Qt.QtCore import *
from Qt.QtWidgets import *
from Qt.QtGui import *

class butt(QPushButton):
    def __init__(self, parent, name='Button'):
        super(self.__class__, self).__init__(parent)
        self.name = name

    def closeEvent(self, e):
        print('butt closeevent')
        e.accept()


class wid(QWidget):
    def __init__(self, parent=None):
        super(self.__class__, self).__init__(parent)
        self.initUI()

    def initUI(self):
        #self.setAttribute(Qt.WA_DeleteOnClose)
        self.vl = QVBoxLayout(self)
        self.button = butt(self)
        self.button.setText('test1')
        self.vl.addWidget(self.button)
        self.button.clicked.connect(QCoreApplication.quit)

    def closeEvent(self, e):
        print('wid closeevent')
        e.accept()


def show():
    app = QApplication(sys.argv)
    win = QMainWindow()
    widget = wid(win)
    win.setCentralWidget(widget)
    win.show()
    app.exec_()


if __name__ == "__main__":
    show()

I'm expecting to see 2 lines wid closeevent butt closeevent as an output, but I see nothing. Why closeEvent it not being called for them?

like image 229
Kthulhu Fhtagn Avatar asked Oct 15 '25 07:10

Kthulhu Fhtagn


1 Answers

In the following examples when you press the button visually you will observe the same behavior: the window will close, but we see the difference, in the first one it is called closeEvent(), and in the second one it is not.

Example1:

#!/bin/env python
# -*- coding: utf-8 -*-

from Qt import QtCore, QtGui, QtWidgets


class Button(QtWidgets.QPushButton):
    def closeEvent(self, event):
        print("button closeEvent")
        event.accept()


def main():
    import sys

    app = QtWidgets.QApplication(sys.argv)
    button = Button(text="Press me")
    button.clicked.connect(button.close)
    button.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

Example2:

#!/bin/env python
# -*- coding: utf-8 -*-

from Qt import QtCore, QtGui, QtWidgets


class Button(QtWidgets.QPushButton):
    def closeEvent(self, event):
        print("button closeEvent")
        event.accept()


def main():
    import sys

    app = QtWidgets.QApplication(sys.argv)
    button = Button(text="Press me")
    button.clicked.connect(QtCore.QCoreApplication.quit)
    button.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

Why do not you call closeEvent when you call QCoreApplication::quit? Because this method is used to exit the event loop of Qt, and if there is no event loop the events(QCloseEvent) do not work.


When a widget is closed, the children widget is not closed, that is, only if a widget is closed, only its own closeEvent will be called. So if you want the closeevent of the widget to be called, call your method close.

#!/bin/env python
# -*- coding: utf-8 -*-

from Qt import QtCore, QtGui, QtWidgets


class Button(QtWidgets.QPushButton):
    def __init__(self, name="Button", parent=None):
        super(Button, self).__init__(parent)
        self.m_name = name

    def closeEvent(self, event):
        print("button closeEvent")
        event.accept()


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

    def initUI(self):
        vl = QtWidgets.QVBoxLayout(self)
        button = Button()
        button.setText("test1")
        vl.addWidget(button)
        button.clicked.connect(self.close)

    def closeEvent(self, event):
        print("Widget closeevent")
        event.accept()


def main():
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = QtWidgets.QMainWindow()
    widget = Widget()
    w.setCentralWidget(widget)
    w.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

In the previous example depending on how you interact with the widget you will get the following behavior:

  • If you press the button the widget will close so it will be called closeEvent, and will not call the closeEvent of the button because even your child is not closing it.

  • If you press the "X" button in the window it will not be called the closeEvent of the widget but the QMainWindow, the explanation is the same as the previous one.

Conclusions:

  • Each type of event has its own workflow, some events are only received by the widget and not by the children, while others send the information to the children.

  • The close method uses the event loop to notify that the widget is to be closed, but QCoreApplication::quit() terminates the event loop.

Update:

why wid.closeEvent is not called when user pressing "X" button on Qt window? Aren't main window supposed to call closeEvent on all it's children widgets and then destroy them properly?

No, one thing is the closing of a widget and another thing is the destruction of the widget, it can be destroyed without closing and closing the window does not involve destroying the object.

As already pointed out closing a window does not imply deleting it, there may be other windows open, but if the last QApplication window is closed by default the eventloop that implies the destruction of the widget will be terminated which does not necessarily imply calling the close method.

To be understood, let's use the following code:

from Qt import QtCore, QtGui, QtWidgets


class Button(QtWidgets.QPushButton):
    def closeEvent(self, event):
        print("closeEvent Button")
        super(Button, self).closeEvent(event)


class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Widget, self).__init__(parent)
        button_quit = Button(
            text="quit", 
            clicked=QtCore.QCoreApplication.quit
        )
        button_close = Button(
            text="close",
            clicked=self.close
        )

        lay = QtWidgets.QVBoxLayout(self)
        lay.addWidget(button_quit)
        lay.addWidget(button_close)

    def closeEvent(self, event):
        print("closeEvent Widget")
        super(Widget, self).closeEvent(event)


if __name__ == "__main__":
    import sys

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

There are 2 buttons, in the case of the first button that calls QCoreApplication::quit() that will end the eventloop so all the widgets will be destroyed, and in that case no closeEvent will be called, in the case of the second button it will be call near the window so it will call its closeEvent but not the closeEvents of its children.

my actual problem is that I have saveUI() func in closeEvent, and it's not being called upon widgets hierarchical destruction on window close

If you want the closeEvent method to be called hierarchically then you must call the close method manually since Qt does not design it that way. In the next part there is an example:

from PyQt5 import QtCore, QtGui, QtWidgets


class PushButton(QtWidgets.QPushButton):
    def closeEvent(self, event):
        for children in self.findChildren(
            QtWidgets.QWidget, options=QtCore.Qt.FindDirectChildrenOnly
        ):
            children.close()
        print("closeEvent PushButton")
        super(PushButton, self).closeEvent(event)


class LineEdit(QtWidgets.QLineEdit):
    def closeEvent(self, event):
        for children in self.findChildren(
            QtWidgets.QWidget, options=QtCore.Qt.FindDirectChildrenOnly
        ):
            children.close()
        print("closeEvent LineEdit")
        super(LineEdit, self).closeEvent(event)


class ComboBox(QtWidgets.QComboBox):
    def closeEvent(self, event):
        for children in self.findChildren(
            QtWidgets.QWidget, options=QtCore.Qt.FindDirectChildrenOnly
        ):
            children.close()
        print("closeEvent ComboBox")
        super(ComboBox, self).closeEvent(event)


class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Widget, self).__init__(parent)
        button_close = PushButton(text="close", clicked=self.close)

        lay = QtWidgets.QVBoxLayout(self)
        lay.addWidget(button_close)
        lay.addWidget(LineEdit())
        lay.addWidget(ComboBox())

    def closeEvent(self, event):
        for children in self.findChildren(
            QtWidgets.QWidget, options=QtCore.Qt.FindDirectChildrenOnly
        ):
            children.close()
        print("closeEvent Widget")
        super(Widget, self).closeEvent(event)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec_())
like image 132
eyllanesc Avatar answered Oct 17 '25 19:10

eyllanesc



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!