python-3.xpyqt5event-handlingqeventeventfilter

PyQt5: how to reimplement close-event in event-filter


I have this Qt Designer made main-window: main.py:

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(364, 480)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName("verticalLayout")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setMinimumSize(QtCore.QSize(341, 71))
        font = QtGui.QFont()
        font.setPointSize(14)
        font.setBold(True)
        font.setItalic(False)
        font.setWeight(75)
        self.label.setFont(font)
        self.label.setLayoutDirection(QtCore.Qt.LeftToRight)
        self.label.setAlignment(QtCore.Qt.AlignCenter)
        self.label.setObjectName("label")
        self.verticalLayout.addWidget(self.label)
        self.models = QtWidgets.QGroupBox(self.centralwidget)
        self.models.setObjectName("models")
        self.gridLayout_2 = QtWidgets.QGridLayout(self.models)
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.scrollArea = QtWidgets.QScrollArea(self.models)
        self.scrollArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
        self.scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setObjectName("scrollArea")
        self.scrollAreaWidgetContents = QtWidgets.QWidget()
        self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 311, 196))
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.scrollAreaWidgetContents.sizePolicy().hasHeightForWidth())
        self.scrollAreaWidgetContents.setSizePolicy(sizePolicy)
        self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
        self.gridLayout = QtWidgets.QGridLayout(self.scrollAreaWidgetContents)
        self.gridLayout.setObjectName("gridLayout")
        spacerItem = QtWidgets.QSpacerItem(296, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.gridLayout.addItem(spacerItem, 0, 0, 1, 1)
        self.scrollArea.setWidget(self.scrollAreaWidgetContents)
        self.gridLayout_2.addWidget(self.scrollArea, 0, 0, 1, 1)
        self.pushButton_parent_none = QtWidgets.QPushButton(self.models)
        self.pushButton_parent_none.setObjectName("pushButton_parent_none")
        self.gridLayout_2.addWidget(self.pushButton_parent_none, 1, 0, 1, 1)
        self.pushButton_parent_main = QtWidgets.QPushButton(self.models)
        self.pushButton_parent_main.setObjectName("pushButton_parent_main")
        self.gridLayout_2.addWidget(self.pushButton_parent_main, 2, 0, 1, 1)
        self.verticalLayout.addWidget(self.models)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 364, 29))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.label.setText(_translate("MainWindow", "Main"))
        self.models.setTitle(_translate("MainWindow", "GroupBox"))
        self.pushButton_parent_none.setText(_translate("MainWindow", "Test-set-parent_None"))
        self.pushButton_parent_main.setText(_translate("MainWindow", "Test-set-parent_Main"))

and this code : myapp.py :

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QDesktopWidget
from main  import Ui_MainWindow

class MyApp(QMainWindow, Ui_MainWindow):

    def __init__(self, parent=None):
        super(MyApp, self).__init__(parent)

        self.setupUi(self)
        
        self.qtRectangle = self.frameGeometry()
        self.centerPoint = QDesktopWidget().availableGeometry().center()
        self.qtRectangle.moveCenter(self.centerPoint)
        self.move(self.qtRectangle.topLeft())

        self.pushButton_parent_none.clicked.connect(
            lambda checked, x=self.models: self.set_parent_none(x))
        self.pushButton_parent_main.clicked.connect(
            lambda checked, x=self.models: self.set_parent_main(x))
        
        self.cnt = 0
        self.models.installEventFilter(self) 
        
        self.show
        
    def eventFilter(self, source, event):

        if source == self.models:

            self.cnt += 1
            # print('filteredddddddddddddd : ', self.cnt, event, type(event), source)
            # print('filteredddddddddddddd', event.type(), event)

            if event.type() == 19:
                print(source.windowTitle())
                
                print(source)

                print('filteredddddddddddddd : ',
                      self.cnt, event.type(), event, type(event), source)

                print('source.parent() :', source.parent())

                self.set_parent_main(source)

                print('source.parent() :', source.parent())
                
                return True

            else:
                return False

        else:
            return False
        
    def closeEvent(self, evnt):

        print('clooossssing main !!')

        evnt.accept()

    def set_parent_main(self, sender):

        print('main before !!!!!! :', sender.parent())

        sender.setParent(self)
        
        print('main after !!!!!! :', sender.parent())

        sender.hide()
        sender.show()

    def set_parent_none(self, sender):
        
        print('none before !!!!! :', sender.parent())
        
        sender.setParent(None)
        
        print('none after !!!!! :', sender.parent())

        sender.show()
        
        
if __name__ == '__main__':

    app = QApplication(sys.argv)
    myapp = MyApp()
    myapp.show()

    try:
        sys.exit(app.exec_())
    except SystemExit:
        print('Closing Window...')

When I click the Test-set-parent_None button the QGroupBox Widget gets detached from MainWindow; and it gets re-attached if I click the Test-set-parent_Main button.

What is puzzling me it's why my eventFilter it's not working. If I close the QGroupBox with the X closing button (top right corner), I get the :

main before !!!!!! : None
main after !!!!!! : <__main__.MyApp object at 0x7ff7f35be550>
source.parent() : <__main__.MyApp object at 0x7ff7f35be550>

print that tells me that my QGroupBox is again the child of my MainWindow - but it doesn't show up. What am I missing in my 'eventFilter' implementation?


Solution

  • Returning True in the event-filter isn't always sufficient to prevent propagation of the event. For close-events, you must also explicitly ignore the event:

    class MyApp(QMainWindow, Ui_MainWindow):
        ...
        def eventFilter(self, source, event):
            if event.type() == QtCore.QEvent.Close and source is self.models:
                self.set_parent_main(source)
                event.ignore()
                return True
            return super().eventFilter(source, event)