关闭时不会调用子窗口小部件的 closeEvent

Kth*_*agn 4 python pyside

要么我不完全理解 Qt 的事件传播是如何工作的,要么是其他什么,但我无法理解为什么 QPushButton 派生类和 QWidget 派生类本身都没有调用 closeEvent。

wid.closeEvent() 不应该触发所有子部件的 closeEvents 吗?

#!/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()
Run Code Online (Sandbox Code Playgroud)

我期望看到 2 行 wid closeevent butt closeevent 作为输出,但我什么也没看到。为什么 closeEvent 没有被调用?

eyl*_*esc 6

在下面的示例中,当您直观地按下按钮时,您将观察到相同的行为:窗口将关闭,但我们看到了区别,在第一个窗口中它被称为 closeEvent(),而在第二个窗口中则不是。

示例1:

#!/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()
Run Code Online (Sandbox Code Playgroud)

示例2:

#!/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()
Run Code Online (Sandbox Code Playgroud)

为什么在调用 QCoreApplication::quit 时不调用 closeEvent? 因为该方法用于退出Qt的事件循环,如果没有事件循环,则events(QCloseEvent)不起作用。


当一个widget关闭时,子widget并没有关闭,也就是说,只有当一个widget关闭时,才会调用它自己的closeEvent。因此,如果您希望调用小部件的 closeevent,请调用您的方法 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()
Run Code Online (Sandbox Code Playgroud)

在前面的示例中,根据您与小部件交互的方式,您将获得以下行为:

  • 如果您按下按钮,小部件将关闭,因此它将被称为 closeEvent,并且不会调用按钮的 closeEvent,因为即使您的孩子也没有关闭它。

  • 如果你按下窗口中的“X”按钮,它不会被称为窗口部件的closeEvent,而是被称为QMainWindow,解释与上一相同。

结论:

  • 每种类型的事件都有自己的工作流程,某些事件仅由小部件接收,而不会由子部件接收,而其他事件则将信息发送给子部件。

  • close 方法使用事件循环来通知小部件要关闭,但 QCoreApplication::quit() 终止事件循环。

更新:

为什么当用户在 Qt 窗口上按“X”按钮时不调用 wid.closeEvent?主窗口不应该在其所有子窗口小部件上调用 closeEvent 然后正确销毁它们吗?

不,一件事是关闭小部件,另一件事是小部件的销毁,它可以在不关闭的情况下被销毁,并且关闭窗口不涉及销毁对象。

正如已经指出的那样,关闭窗口并不意味着删除它,可能还有其他窗口打开,但是如果默认情况下关闭最后一个 QApplication 窗口,则意味着小部件销毁的事件循环将被终止,这并不一定意味着调用 close方法。

为了便于理解,我们使用以下代码:

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_())
Run Code Online (Sandbox Code Playgroud)

有 2 个按钮,在第一个按钮调用 QCoreApplication::quit() 的情况下,它将结束事件循环,因此所有小部件将被销毁,在这种情况下,不会调用 closeEvent,在第二个按钮的情况下它将在窗口附近调用,因此它将调用其 closeEvent,但不会调用其子级的 closeEvent。

我的实际问题是我在 closeEvent 中有 saveUI() 函数,并且在窗口关闭时小部件层次结构破坏时没有调用它

如果您希望分层调用 closeEvent 方法,那么您必须手动调用 close 方法,因为 Qt 没有这样设计。下一部分有一个例子:

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_())
Run Code Online (Sandbox Code Playgroud)

  • 好吧,我有点不清楚:按钮的行为在我的情况下并不重要,这只是一个例子。该小部件应该内置到其他 UI 元素/小部件中。所以基本上问题可以这样改写:为什么当用户在 Qt 窗口上按“X”按钮时不调用 wid.closeEvent?主窗口不应该在其所有子窗口小部件上调用 closeEvent 然后正确销毁它们吗?(这是一个简化的示例,我的实际问题是我在 closeEvent 中有 saveUI() 函数,并且在窗口关闭时小部件层次结构破坏时没有调用它) (2认同)