QTreeWidget removeItemWidget 后需要删除widget 吗?

Do you need to delete widget after removeItemWidget from QTreeWidget?

我有一个包含两列的 QTreeWidget:一列用于 属性 名称,一列用于 属性 值。该值可以通过小部件进行编辑。例如一个 属性 是动物。当您双击 属性 值列时,我通过以下代码制作了一个包含不同动物类型的(自定义)组合框:

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);

当该行失去焦点时,我再次删除小部件(并且该值作为 QTreeWidgetItem 的文本)。为了删除我使用

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

现在我想知道,既然我已经使用了 new,我是否还需要 delete 这个小部件。我知道如果您使用 takeChild(i) 就是这种情况。但是我没有看到 itemWidget 的类似内容。

我需要删除它吗?正确的顺序是什么?

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;

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);

您不能删除项目小部件,因为一旦使用 setItemWidget() 传递给树,树就是小部件的所有者。

来自 setItemWidget() 的文档:

Note: The tree takes ownership of the widget.

编辑:如果您想要一个新的小部件,只需再次调用 setItemWidget() 或调用 removeItemWidget() 以防您不再需要该小部件.这棵树将确保不会丢失任何记忆。

当小部件添加到 QTreeWidget 时,它确实取得了小部件的所有权。但是它只是意味着当父级被销毁时widget将被删除

因此,如果您只是想在保持父 QTreeWidget 活动的同时删除小部件,您确实必须手动删除它。

正确的解决方法是第一种,先从QTreeWidget中删除widget,然后通过以下方式之一删除它:

delete comboBox;
comboBox = nullptr;

或:

comboBox.deleteLater();

第二个优先


编辑:

我不会更改答案,因为更改已经接受的内容可能是不诚实的,...

但是正如@Scopchanov提到的,通过阅读源代码,QTreeWidget::removeItemWidget()已经调用了deleteLater() 旧小部件上的方法。我们不必手动完成。

无论如何,the documentation 表示多次调用 deleteLater() 是安全的:

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.

因此,调用QTreeWidget::removeItemWidget()后手动删除widget就没有用了。

说明

不应该 手动删除添加到 QTreeWidget 的小部件,因为它 自动删除

  • 销毁其父树小部件

这是 Qt 父子机制的直接结果。

  • 在树小部件仍然存在的任何时候调用 QTreeWidget::removeItemWidget

这个不是很明显,因为 documentation 只是说:

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

但是,查看 source code 就会很清楚确实发生了什么,即

  1. QTreeWidget::removeItemWidget 使用 null 指针调用 QTreeWidget::setItemWidget(无小部件)

    inline void QTreeWidget::removeItemWidget(QTreeWidgetItem *item, int column)
    { setItemWidget(item, column, nullptr); }
    
  2. QTreeWidget::setItemWidget 依次调用 QAbstractItemView::setIndexWidget

    void QTreeWidget::setItemWidget(QTreeWidgetItem *item, int column, QWidget *widget)
    {
        Q_D(QTreeWidget);
        QAbstractItemView::setIndexWidget(d->index(item, column), widget);
    }
    
  3. 最后QAbstractItemView::setIndexWidget检查这个索引处是否已经有一个widget,如果有就调用它的deleteLater方法

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

简单地说(这应该在 QTreeWidget 的两种方法的文档中明确说明),任何对 QTreeWidget::setItemWidgetQTreeWidget::removeItemWidget 的调用都会删除小部件(如果有的话)已经为项目设置.

例子

这是我为您准备的一个简单示例,用于演示所描述的行为:

#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();
}

结果

可以使用应用程序测试所描述的销毁小部件的方法

  • 简单地关闭 window 会破坏树部件及其子组合框,因此会发出组合框的销毁信号并且 lambda 打印

The combo box is gone.

  • 按下按钮后,调用连接到其 clicked 信号的 lambda 函数,从树形小部件中删除组合框。因为组合框也被(自动)删除,所以调用了第二个 connect 语句的 lambda,它也打印了

The combo box is gone.