Search code examples
pythonmemory-leakspyqtpyqt5qtabwidget

Dynamically created tabs - destroy object when tab closed


I am dynamically creating multiple tabs, and I was wondering if when I close the tab, does it actually destroy the object?

I have set the attribute of the dynamically created tab to QtCore.Qt.WA_DeleteOnClose

I assume that setting the attribute doesn't do anything because the closeEvent isn't called on it.

    self.tabWidget.tabCloseRequested.connect(self.close_handler) # connect close button to slot

    def close_handler(self, index):
        'Remove added tab if applicable'
        try:
            #self.tabWidget.widget(index).deleteLater() #does this need to be added?
            self.tabWidget.removeTab(index)
        except:
            Data.logger.exception('Exception occured:')

How do I make sure that the object is being destroyed?


Solution

  • My answer is oriented to explain details that the other answer does not indicate so that you understand how it handles the memory Qt.

    If you want to know if a QObject is eliminated then you must use the destroyed signal.

    If you want to delete a QObject you must use the deleteLater() method.

    In the case of the elimination of a widget added to the QTabWidget this will notify the QTabBar that it will be deleted so there are no problems of accessing non-reserved memory.

    Considering the above, a possible solution is:

    def close_handler(self, index):
        widget = self.tabWidget.widget(index)
        widget.deleteLater()
    

    You can check it with the following example:

    from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super(MainWindow, self).__init__(parent)
    
            self.tabWidget = QtWidgets.QTabWidget(tabsClosable=True)
            self.tabWidget.tabCloseRequested.connect(self.onTabCloseRequested)
            self.setCentralWidget(self.tabWidget)
    
            for i in range(10):
                widget = QtWidgets.QWidget()
                widget.destroyed.connect(
                    lambda obj: print(
                        "deleted {}, count: {}".format(obj, self.tabWidget.count())
                    )
                )
                self.tabWidget.addTab(widget, "Tab %s" % (i))
    
        @QtCore.pyqtSlot(int)
        def onTabCloseRequested(self, index):
            widget = self.tabWidget.widget(index)
            widget.deleteLater()
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = MainWindow()
        w.resize(640, 480)
        w.show()
        sys.exit(app.exec_())
    

    As you can see, it is not necessary to use the attribute Qt::WA_DeleteOnClose, but if you use it, it would be enough to close the widget.

    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super(MainWindow, self).__init__(parent)
    
            self.tabWidget = QtWidgets.QTabWidget(tabsClosable=True)
            self.tabWidget.tabCloseRequested.connect(self.onTabCloseRequested)
            self.setCentralWidget(self.tabWidget)
    
            for i in range(10):
                widget = QtWidgets.QWidget()
                widget.setAttribute(QtCore.Qt.WA_DeleteOnClose)
                widget.destroyed.connect(
                    lambda obj: print(
                        "deleted {}, count: {}".format(obj, self.tabWidget.count())
                    )
                )
                self.tabWidget.addTab(widget, "Tab %s" % (i))
    
        @QtCore.pyqtSlot(int)
        def onTabCloseRequested(self, index):
            widget = self.tabWidget.widget(index)
            widget.close()
    

    It is not necessary to use removeTab() since this does not destroy the widget, that method is thought so that when you want to place the widget removed to another widget.