Do you need to delete widget after removeItemWidget from QTreeWidget?

Viewed 1221

I have a QTreeWidget with two columns: one for property name and one for property value. The value can be edited via a widget. For example one property is Animal. When you double click the property value column I make a (custom) combobox with different animal types via this code:

QTreeWidgetItemComboBox* comboBox = new QTreeWidgetItemComboBox(treeItem, 1); 
// treeitem is a pointer to the row that is double clicked
comboBox->addItems(QStringList() << "Bird" << "Fish" << "Ape");
ui.treeWidget->setItemWidget(treeItem, 1, comboBox);

When the row loses focus I remove the widget again (and the value is put as text of the QTreeWidgetItem). For removing I use

ui.treeWidget->removeItemWidget(treeItem, 1);

Now I'm wondering, since I've used new, do I neww to also delete the widget. I know this is the case if you use takeChild(i) for example. But I didn't see something similar for an itemWidget.

Do I need to delete it what would be the right order?

QTreeWidgetItemComboBox* comboBox = ui.treeWidget->itemWidget(treeItem,1); 
// Do I need a cast here since the return type is QWidget*
ui.treeWidget->removeItemWidget(treeItem, 1);
delete comboBox;

or

QTreeWidgetItemComboBox* comboBox = ui.treeWidget->itemWidget(treeItem,1); 
// Do I need a cast here since the return type is QWidget*
delete comboBox;
ui.treeWidget->removeItemWidget(treeItem, 1);
3 Answers

You are not allowed to delete the item widget as the tree is the owner of the widget once it has been passed to the tree with setItemWidget().

From the documentation of setItemWidget():

Note: The tree takes ownership of the widget.

EDIT: In case you want a new widget, simply call setItemWidget() once more or call removeItemWidget() in case you do not need the widget anymore. The tree will ensure that no memory gets lost.

When the widget is added ot the QTreeWidget, it indeed takes ownership of the widget. But it only implies that the widget will be deleted when the parent is destroyed.

So if you just want to remove the widget while keeping the parent QTreeWidget alive, you indeed have to delete it manually.

The correct solution is the first one, remove the widget from the QTreeWidget first, and then delete it with one of the following ways:

delete comboBox;
comboBox = nullptr;

or:

comboBox.deleteLater();

The second one is preferred.


EDIT:

I don't change the answer since it could be a dishonest to change what was already accepted, ...

But as @Scopchanov mentioned, by reading the source code, the QTreeWidget::removeItemWidget() already calls the deleteLater() method on the old widget. We don't have to do it manually.

Anyway, the documentation says it is safe to call deleteLater() more than once:

Note: It is safe to call this function more than once; when the first deferred deletion event is delivered, any pending events for the object are removed from the event queue.

Therefore, manually deleting the widget after calling QTreeWidget::removeItemWidget() becomes useless.

Explaination

You should not manually delete a widget, added to a QTreeWidget, since it is automatically deleted either by

  • destructing its parent tree widget

This is a direct consequence of the Qt's parent-child mechanism.

  • calling QTreeWidget::removeItemWidget anytime the tree widget still lives.

This one is not so obvious, since the documentation simply sais:

Removes the widget set in the given item in the given column.

However, looking at the source code it becomes pretty clear what is indeed happening, i.e.

  1. QTreeWidget::removeItemWidget calls QTreeWidget::setItemWidget with a null pointer (no widget)

    inline void QTreeWidget::removeItemWidget(QTreeWidgetItem *item, int column)
    { setItemWidget(item, column, nullptr); }
    
  2. QTreeWidget::setItemWidget in turn calls QAbstractItemView::setIndexWidget

    void QTreeWidget::setItemWidget(QTreeWidgetItem *item, int column, QWidget *widget)
    {
        Q_D(QTreeWidget);
        QAbstractItemView::setIndexWidget(d->index(item, column), widget);
    }
    
  3. Finally QAbstractItemView::setIndexWidget checks if there is already a widget at this index, and if there is one, calls its deleteLater method

    if (QWidget *oldWidget = indexWidget(index)) {
        d->persistent.remove(oldWidget);
        d->removeEditor(oldWidget);
        oldWidget->removeEventFilter(this);
        oldWidget->deleteLater();
    }
    

Simply put (and this should be made clear in the documentation of both methods of QTreeWidget), any call to QTreeWidget::setItemWidget or QTreeWidget::removeItemWidget deletes the widget (if any) already set for the item.

Example

Here is a simple example I have prepared for you in order to demonstrate the described behaviour:

#include <QApplication>
#include <QBoxLayout>
#include <QTreeWidget>
#include <QComboBox>
#include <QPushButton>

struct MainWindow : public QWidget
{
    MainWindow(QWidget *parent = nullptr) : QWidget(parent) {
        auto *l = new QVBoxLayout(this);
        auto *treeWidget = new QTreeWidget(this);
        auto *item = new QTreeWidgetItem(treeWidget);
        auto *button = new QPushButton(tr("Remove combo box"), this);
        auto *comboBox = new QComboBox();

        comboBox->addItems(QStringList() << "Bird" << "Fish" << "Ape");
        treeWidget->setItemWidget(item, 0, comboBox);
        l->addWidget(button);
        l->addWidget(treeWidget);

        connect(comboBox, &QComboBox::destroyed, [](){
            qDebug("The combo box is gone.");
        });

        connect(button, &QPushButton::clicked, [treeWidget, item](){
            treeWidget->removeItemWidget(item, 0);
        });

        resize(400, 300);
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

Result

The described ways of destroyng the widget could be tested with the application

Application window with a button and a combo box in a tree widget

  • Simply closing the window destroys the tree widget together with its child combo box, hence the combo box's destroyed signal is emitted and the lambda prints

The combo box is gone.

  • After pressing the button the lambda function connected to its clicked signal is called, which removes the combo box from the tree widget. Because the combo box is deleted (automatically) as well, the lambda from the second connect statement is called, which also prints

The combo box is gone.

Related