Вам нужно удалить виджет после removeItemWidget из QTreeWidget? - PullRequest
4 голосов
/ 07 октября 2019

У меня есть QTreeWidget с двумя столбцами: один для имени свойства и один для значения свойства. Значение можно редактировать с помощью виджета. Например, одно свойство это Animal. Когда вы дважды щелкаете столбец значения свойства, я создаю (настраиваемый) комбинированный список с различными типами животных с помощью этого кода:

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

Ответы [ 3 ]

3 голосов
/ 07 октября 2019

Когда виджет добавляется к QTreeWidget, он действительно становится владельцем виджета. Но означает, что виджет будет удален только после уничтожения родителя.

Так что, если вы просто хотите удалить виджет, сохранив при этом родительский элемент QTreeWidget, вам действительно нужноудалите его вручную.

Правильное решение - первое, сначала удалите виджет из QTreeWidget, а затем удалите его одним из следующих способов:

delete comboBox;
comboBox = nullptr;

или:

comboBox.deleteLater();

Второй вариант предпочтительнее.


РЕДАКТИРОВАТЬ:

Я не изменяю ответ, так как онможет быть нечестным изменить то, что уже было принято, ...

Но, как @ Scopchanov упомянул, прочитав исходный код, QTreeWidget::removeItemWidget() уже вызывает метод deleteLater() для старого виджета. Нам не нужно делать это вручную.

В любом случае, в документации говорится, что можно звонить deleteLater() более одного раза:

Примечание: Можно вызывать эту функцию более одного раза;при доставке первого отложенного события удаления все ожидающие события для объекта удаляются из очереди событий.

Поэтому ручное удаление виджета после вызова QTreeWidget::removeItemWidget() становится бесполезным.

3 голосов
/ 07 октября 2019

Вам не разрешено удалять виджет элемента, поскольку дерево является владельцем виджета, как только он был передан в дерево с setItemWidget().

Из документации setItemWidget():

Примечание. Дерево становится владельцем виджета.

РЕДАКТИРОВАТЬ : если вам нужен новый виджет, просто позвоните setItemWidget() еще раз илипозвоните removeItemWidget(), если вам больше не нужен виджет. Дерево гарантирует, что память не будет потеряна.

2 голосов
/ 07 октября 2019

Объяснение

Вы не должны вручную удалять виджет, добавленный к QTreeWidget , поскольку он автоматически удаляется либо

  • уничтожение виджета его родительского дерева

Это прямое следствие механизма Qt parent-child.

  • вызов QTreeWidget::removeItemWidget в любое время деревавиджет все еще живет.

Этот не так очевиден, так как документация просто говорит:

Удаляет виджет, установленный в данном элементе вданный столбец.

Однако, глядя на исходный код , становится довольно ясно, что на самом деле происходит, то есть

  1. QTreeWidget::removeItemWidget звонки QTreeWidget::setItemWidget с указателем null (без виджета)

    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 проверяет, есть ли уже виджет с этим индексом, и, если он есть, вызывает его deleteLater метод

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

Проще говоря (и это должно быть ясно указано в документации обоих методов QTreeWidget), любой вызов QTreeWidget::setItemWidget или QTreeWidget::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();
}

Результат

Описанные способы уничтожения виджета можно протестировать с помощью приложения

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

  • Простое закрытие окна разрушает виджет деревавместе со своим дочерним комбинированным списком, следовательно, испускается разрушенный сигнал комбинированного блока, и лямбда печатает

The combo box is gone.

  • После нажатия кнопки лямбда-функция, связанная с егоВызывается сигнал clicked, который удаляет поле со списком из виджета дерева. Поскольку поле со списком также удаляется (автоматически), вызывается лямбда из второго оператора connect, который также печатает

The combo box is gone.

...