Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

tabifyDockWidget() adding unknown widgets



  • Hello,

    I am fighting against an issue for several days without understanding the issue. Even if I delete QDockWidgets, the number of widgets is always increasing. Could you help me ? Thanks in advance !

    I have a vector of Qt panels stored in a vector:

    myWindow {
    public:
    	void rebuildContent();
    
    private:
    	std::vector<uncloseableDockWidget *> m_widgets;
    }
    

    uncloseableDockWidget is an interface impeding QDockWidgets to be closed.

    class uncloseableDockWidget : public QDockWidget {
        Q_OBJECT
    
    public:
        explicit uncloseableDockWidget(const QString & title, QWidget * parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()) : QDockWidget(title, parent, flags) {
            setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
            setAllowedAreas(Qt::AllDockWidgetAreas);
        }
    
        virtual ~uncloseableDockWidget() override {
        }
    
    public slots:
        virtual void updateView() = 0;
    
    protected:
        void closeEvent(QCloseEvent * event) override {
    	event->ignore();
        }
    };
    

    Now, coming back to my main window, when calling rebuildContent, I clear the QDockWidgets and build new ones:

    void myWindow::rebuildContent() {
        std::cout << "test1 " << QApplication::allWidgets().size() << std::endl;
        // Remove already existing dock widgets
        for(uncloseableDockWidget * w:m_widgets) {
            m_container->removeDockWidget(w);
            delete w;
        }
        m_widgets.clear();
        std::cout << "test2 " << QApplication::allWidgets().size() << std::endl;
        // Add new widgets
        m_widgets.emplace_back(new customDockWidget(tr("widget1")));
        m_widgets.emplace_back(new customDockWidget(tr("widget2")));
        // Connect signals
        std::cout << "test3 " << QApplication::allWidgets().size() << std::endl;
        for(uncloseableDockWidget * w:m_widgets)
            connect(w, SIGNAL(updateViewRequest()), this, SLOT(updateView()));
        std::cout << "test4 " << QApplication::allWidgets().size() << std::endl;
        // Add and tabify dock widgets
        for(uncloseableDockWidget * w:m_widgets)
            m_container->addDockWidget(Qt::RightDockWidgetArea, w);
        std::cout << "test5 " << QApplication::allWidgets().size() << std::endl;
        for(unsigned int i=1; i<m_widgets.size(); i++)
            m_container->tabifyDockWidget(m_widgets[i-1], m_widgets[i]);
        std::cout << "test6 " << QApplication::allWidgets().size() << std::endl;
        if(m_listOfDockWidgets.size())
            m_widgets.front()->raise();
        std::cout << "test7 " << QApplication::allWidgets().size() << std::endl;
    }
    

    When I run function 'rebuildContent' twice, here is the result:

    test1 305
    test2 305 -> no widget in m_widgets for first call 
    test3 365 -> build 60 widgets
    test4 365 -> nothing done (just connecting things)
    test5 365 -> nothing done (adding QDockWidgets)
    test6 368 -> I do not understand why 3 widgets are created when tabifying
    test7 368 -> nothing done (just raising tab)
    ---
    test1 368 -> same amount as before
    test2 308 -> removing the 60 widgets however, the 3 widgets built due to tabifying remains
    test3 368 -> build 60 widgets
    test4 368
    test5 368
    test6 371 -> 3 new widgets due to tabifying...
    test7 371
    

    Is there a reason to get these 3 additional widgets and why the removeDockWidget does not delete it automatically ?


  • Lifetime Qt Champion

    Hi,

    You are building objects that are customWidget are you putting anything in there that is parented differently ?

    Without seing the whole code it's difficult to determine what might be happening.



  • Hello SGaist, when I comment the "tabifyDockWidget" line, I do not have any issue.

    Code inside the customWidgets is quite simple.

    An example:

        QWidget * widget = new QWidget();
        setWidget(widget);
        QVBoxLayout * layout = new QVBoxLayout();
        widget->setLayout(layout);
         m_map = new baseItemMap(); // With m_map beeing a QCustomPlot figure
         layout->addWidget(m_map);
    

    Another example:

        QScrollArea * scroll = new QScrollArea();
        setWidget(scroll);
        scroll->setBackgroundRole(QPalette::Light);
        scroll->setFrameShape(QFrame::NoFrame);
        scroll->setWidgetResizable(true);
        QWidget * widget = new QWidget();
        scroll->setWidget(widget);
        QVBoxLayout * layout = new QVBoxLayout();
        widget->setLayout(layout);
    
        QLabel * labTX = new QLabel(tr("<b>Text</b>"));
        layout->addWidget(labTX);
    
        m_table = new customTableView(); // Derived from a QTableView
        layout->addWidget(m_table);
    


  • I've just tried with empty customWidget and I continue to "leak" widgets.


  • Lifetime Qt Champion

    I just realized, when you call tabify, there's a QTabWidget that is created and tabs added for each widget you put together.

    There you have the three widgets.



  • Thanks SGaist, that's an interesting point ! So, how could I remove these QTabWidgets ? Do I have to do it before or after the following piece of code ?

     for(uncloseableDockWidget * w:m_widgets) {
          m_container->removeDockWidget(w);
          delete w;
    }
    m_widgets.clear();
    


  • I tried to remove the central widget like this :

    delete m_container->centralWidget();
    

    and build a new one. I thought that these additional widgets would be "parented" to the central widget. However, it does not work. Any idea to delete it ?


  • Lifetime Qt Champion

    Well, I think you have found an edge case. From the quick look I did to the sources of QMainWindow and its internal classes, the fact that you are deleting the dock widget every time seems to not be handled in the "tabification" part (I have to take a deeper dive to ensure that).

    I must say that it's pretty unusual to completely destroy dock widgets and recreate them like you are currently doing.

    Would you mind providing a minimal compilable example to reproduce that ?



  • Thanks SGaist, here is a complete exemple :

    main.cpp:

    #include "mainwindow.h"
    #include <QApplication>
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        MainWindow w;
        w.show();
    
        return a.exec();
    }
    

    mainwindow.h

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <iostream>
    #include <QMainWindow>
    #include <QDockWidget>
    #include <QCloseEvent>
    #include <QLabel>
    #include <QPushButton>
    #include <QBoxLayout>
    #include <QApplication>
    
    class uncloseableDockWidget : public QDockWidget {
        Q_OBJECT
    
    public:
        /**
         * @brief Constructor
         * @param title: title
         * @param parent: parent widget
         * @param flags: flags
         */
        explicit uncloseableDockWidget(const QString & title, QWidget * parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()) : QDockWidget(title, parent, flags) {
            setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
            setAllowedAreas(Qt::AllDockWidgetAreas);
        }
    
        /**
         * @brief Destructor
         */
        virtual ~uncloseableDockWidget() override {}
    
    public slots:
        /**
         * @brief Update view
         */
        virtual void updateView() = 0;
    
    protected:
        /**
         * @brief Close event
         * @param event: event
         */
        void closeEvent(QCloseEvent * event) override {
            event->ignore();
        }
    };
    
    class dock : public uncloseableDockWidget {
        Q_OBJECT
    
    public:
        dock(const QString & title, QWidget * parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()) : uncloseableDockWidget(title, parent, flags) {}
    
        void updateView() override {}
    };
    
    class tab1 : public QWidget {
        Q_OBJECT
    
    public:
        tab1(QWidget * parent = nullptr) : QWidget(parent) {
            QVBoxLayout * layout = new QVBoxLayout();
            setLayout(layout);
            QPushButton * bt = new QPushButton("test");
            layout->addWidget(bt);
            connect(bt, SIGNAL(clicked()), this, SIGNAL(btClicked()));
        }
    
    signals:
        void btClicked();
    };
    
    class tab2 : public QWidget {
        Q_OBJECT
    
    public:
        tab2(QWidget * parent = nullptr) : QWidget(parent) {
            QVBoxLayout * layout = new QVBoxLayout();
            setLayout(layout);
            m_content = new QMainWindow();
            layout->addWidget(m_content);
            m_content->setCentralWidget(new QLabel("test"));
        }
    
    public slots:
        void update() {
            std::cout << "test1: " << QApplication::allWidgets().size() << std::endl;
            // Remove already existing dock widgets
            for(uncloseableDockWidget * w:m_docks) {
                m_content->removeDockWidget(w);
                delete w;
            }
            m_docks.clear();
            std::cout << "test2: " << QApplication::allWidgets().size() << std::endl;
            // Add new dock widgets
            m_docks.emplace_back(new dock("title1"));
            m_docks.emplace_back(new dock("title2"));
            m_docks.emplace_back(new dock("title3"));
            std::cout << "test3: " << QApplication::allWidgets().size() << std::endl;
            // Add and tabify dock widgets
            for(uncloseableDockWidget * w:m_docks)
                m_content->addDockWidget(Qt::RightDockWidgetArea, w);
            std::cout << "test4: " << QApplication::allWidgets().size() << std::endl;
            for(unsigned int i=1; i<m_docks.size(); i++)
                m_content->tabifyDockWidget(m_docks[i-1], m_docks[i]);
            std::cout << "test5: " << QApplication::allWidgets().size() << std::endl;
            if(m_docks.size())
                m_docks.front()->raise();
            std::cout << "test6: " << QApplication::allWidgets().size() << std::endl;
        }
    
    private:
        QMainWindow * m_content = nullptr;
        std::vector<uncloseableDockWidget *> m_docks;
    };
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        MainWindow(QWidget * /*parent*/ = nullptr) {
            m_tab = new QTabWidget();
            setCentralWidget(m_tab);
            tab1 * t1 = new tab1();
            m_tab->addTab(t1, "t1");
            tab2 * t2 = new tab2();
            m_tab->addTab(t2, "t2");
            connect(t1, SIGNAL(btClicked()), t2, SLOT(update()));
        }
    
        ~MainWindow() {
    
        }
    private:
        QTabWidget * m_tab = nullptr;
    };
    
    #endif // MAINWINDOW_H
    

    Clicking various times on the "test" button in tab 1, you can see that number of widgets increases.



  • Hello SGaist, is the example I provided enough to understand my issue ? Do you think I could get a workaround in order to avoid "leaking" widgets ? Thnanks in advance.


  • Lifetime Qt Champion

    So it looks like I never submitted my answer, sorry !

    I took a look at the internals of QMainWindow and the handling of the QDockWidget "tabification" is more involved than what I thought.

    There's no easy workaround per se but there's something I though about: do you really need to delete these dock widgets all the time ? Would a reset of their content be enough ? If you really need to delete them, what about just replacing the widget they contain ? Doing so, you would only have a set of QDockWidget and thus avoid the issue you are currently having.

    In any case, I think it would be worth checking the bug report system to see if there's something related and if not, please open a repot providing your small code sample (including the corresponding pro file).



  • Thanks SGaist for you answers ! I can change the way I manage the dock widgets (I could just clear the QMainWindow which contains these docks even if I think it will not be as elegant as my first solution). I'll open a bug report as you propose.


  • Lifetime Qt Champion

    I was rather thinking about cleaning the widgets within the dock widgets. So you keep the number of QDockWidgets constant.


Log in to reply