4

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);
scopchanov
  • 7,966
  • 10
  • 40
  • 68
C. Binair
  • 419
  • 4
  • 14

3 Answers3

3

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.

Jens
  • 6,173
  • 2
  • 24
  • 43
  • So what do you mean? Can I not even do ```removeItemWidget()```? – C. Binair Oct 07 '19 at 13:35
  • Furthermore, I ```new``` every time an treeWidgetItem is double clicked so will that not raise memory problems? – C. Binair Oct 07 '19 at 13:39
  • 1
    @C.Binair `QTreeWidget::removeItemWidget()` will not delete the widget. If you have no use of it, you can write `comboBox.deleteLater();` right after removing it from the parent `QTreeWidget`. – Fareanor Oct 07 '19 at 13:47
  • @Fareanor, _`QTreeWidget::removeItemWidget()` will not delete the widget_ This is not true. See my answer for an explanation. – scopchanov Oct 07 '19 at 15:48
  • 1
    @C.Binair, _I new every time an treeWidgetItem is double clicked so will that not raise memory problems_ Each time you set a new widget, the old one is deleted. See my answer for an explanation. – scopchanov Oct 08 '19 at 00:52
3

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.

Fareanor
  • 5,900
  • 2
  • 11
  • 37
  • One additional question. I need to retrieve the pointer to the widget. If I do: ```QTreeWidgetItemComboBox* comboBox = ui.treeWidget->itemWidget(treeItem,1); ``` I get an error that a value of type ```QWidget*``` can not be used to initialize an entity of type ```QTreeWidgetItemComboBox*```. Should I use a cast or should I just write ```QWidget* comboBox = ui.treeWidget->itemWidget(treeItem,1); ```? With pointers i'm never super sure about the type in such cases – C. Binair Oct 07 '19 at 14:10
  • @C.Binair It depends. If you need to access `QTreeWidgetItemComboBox` specific methods, you'll need to do a cast (`qobject_cast(ui.treeWidget->itemWidget(treeItem, 1))` for example). – Fareanor Oct 07 '19 at 14:32
  • No I only want to delete it. So then using ```QWidget*``` would be just right I guess – C. Binair Oct 07 '19 at 14:34
  • @C.Binair Since the destructors are `virtual`, you can use polymorphism, so yes, you can directly delete the widget without casting it. – Fareanor Oct 07 '19 at 14:36
  • @C.Binair I have edited my answer since I made a mistake. Just in order to not let you be in error with what I said :) – Fareanor Oct 08 '19 at 07:16
2

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.

scopchanov
  • 7,966
  • 10
  • 40
  • 68