Как иметь QTreeWidgetItems разной высоты в QTreeWidget, используя QStyledItemDelegate?

ПРИМЕЧАНИЕ: оказалось, что проблема не в реализации QStyledItemDelegate, а в том, что в конструкторе MyTreeWidget я вызывал setUniformRowHeights(true). Приведенный ниже код и решение, опубликованное @scopchanov, действительны и работают

QTreeWidget имеет защищенный метод itemFromIndex(), и вот как я делаю его доступным:

class MyTreeWidget : public QTreeWidget {
    Q_OBJECT
public:
    MyTreeWidget(QWidget *parent) : QTreeWidget(parent) {
        setItemDelegate(new MyItemDelegate(this));
    }

    QTreeWidgetItem treeWidgetItemFromIndex(const QModelIndex& index) {
        return itemFromIndex(index);
    }
}

В моем QStyledItemDelegate я сохраняю указатель на MyTreeWidget, а затем переопределяю его виртуальный метод sizeHint() и на основе типа QTreeWidgetItem добавляю дополнение.

class MyItemDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    MyItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {
        _myTreeWidget = dynamic_cast<MyTreeWidget*>(parent);
    }

    QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const {
        auto treeWidgetItem = _myTreeWidget->treeWidgetItemFromIndex(index);
        QSize padding;
        if (dynamic_cast<MyCustomTreeWidgetItem1*>(treeWidgetItem) {
            padding = {0, 5};
        } else if (dynamic_cast<MyCustomTreeWidgetItem2*>(treeWidgetItem) {
            padding = {0, 10};
        }

        return QStyledItemDelegate::sizeHint(option, index) + padding;
    }
}

Это не работает, так как sizeHint() делегата не вызывается для каждого отдельного QTreeWidgetItem.

Итак, мои текстовые опции для вызова setSizeHint() в конструкторе MyCustomTreeWidgetItem1, похоже, тоже не имели никакого эффекта. Qt игнорирует его, потому что есть делегат?

Другим вариантом было установить минимальную высоту QWidget, содержащуюся в MyCustomTreeWidgetItem, что стало возможным благодаря QTreeWidget::setItemWidget().

Таким образом, похоже, что в тот момент, когда я использую делегат, я ограничен только размером. Могу ли я избавиться от делегата или есть что-то еще, что я могу попробовать?

Я знаю, что многие люди сказали бы перейти с QTreeWidget на QTreeView, но в данный момент это невозможно.


person aalimian    schedule 29.10.2020    source источник


Ответы (1)


Решение

Я бы подошел к этой проблеме другим (более простым) способом:

  1. Определите перечисление для различных размеров элементов, например:

     enum ItemType : int {
         IT_ItemWithRegularPadding,
         IT_ItemWithBigPadding
     };
    
  2. При создании элемента задайте в его пользовательских данных желаемый размер в зависимости от его типа, например:

     switch (type) {
     case IT_ItemWithRegularPadding:
         item->setData(0, Qt::UserRole, QSize(0, 5));
         break;
     case IT_ItemWithBigPadding:
         item->setData(0, Qt::UserRole, QSize(0, 10));
         break;
     }
    
  3. В повторной реализации sizeHint извлеките желаемый размер из данных индекса, например:

     QSize sizeHint(const QStyleOptionViewItem &option,
                    const QModelIndex &index) const override {
         return QStyledItemDelegate::sizeHint(option, index)
                 + index.data(Qt::UserRole).toSize();
     }
    

Пример

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

#include <QApplication>
#include <QStyledItemDelegate>
#include <QTreeWidget>
#include <QBoxLayout>

class Delegate : public QStyledItemDelegate
{
public:
    explicit Delegate(QObject *parent = nullptr) :
        QStyledItemDelegate(parent){
    }

    QSize sizeHint(const QStyleOptionViewItem &option,
                   const QModelIndex &index) const override {
        return QStyledItemDelegate::sizeHint(option, index)
                + index.data(Qt::UserRole).toSize();
    }
};

class MainWindow : public QWidget
{
public:
    enum ItemType : int {
        IT_ItemWithRegularPadding,
        IT_ItemWithBigPadding
    };

    MainWindow(QWidget *parent = nullptr) :
        QWidget(parent) {
        auto *l = new QVBoxLayout(this);
        auto *treeWidget = new QTreeWidget(this);
        QList<QTreeWidgetItem *> items;

        for (int i = 0; i < 10; ++i)
            items.append(createItem(QString("item: %1").arg(i),
                                    0.5*i == i/2 ? IT_ItemWithRegularPadding
                                                 : IT_ItemWithBigPadding));

        treeWidget->setColumnCount(1);
        treeWidget->setItemDelegate(new Delegate(this));
        treeWidget->insertTopLevelItems(0, items);

        l->addWidget(treeWidget);

        resize(300, 400);
        setWindowTitle(tr("Different Sizes"));
    }

private:
    QTreeWidgetItem *createItem(const QString &text, int type) {
        auto *item = new QTreeWidgetItem(QStringList(text));

        switch (type) {
        case IT_ItemWithRegularPadding:
            item->setData(0, Qt::UserRole, QSize(0, 5));
            break;
        case IT_ItemWithBigPadding:
            item->setData(0, Qt::UserRole, QSize(0, 10));
            break;
        }

        return item;
    }
};

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

Примечание. В этом примере размер элемента задается в зависимости от его индекса — нечетного или четного. Не стесняйтесь изменить это, внедрив логику, необходимую для различения элементов.

Результат

Данный пример дает следующий результат:

Виджет-дерево с элементами разного размера

Четные и нечетные элементы имеют разную высоту.

person scopchanov    schedule 30.10.2020
comment
Я смог разобраться в своей проблеме, проблема заключалась в том, что мой QTreeWidget устанавливал для свойства uniformRowHeights значение true. Снятие решило проблему. - person aalimian; 30.10.2020
comment
Однако ваше решение устраняет необходимость в dynamic_cast, поэтому я проголосовал за него. - person aalimian; 30.10.2020
comment
@ArmaniStyles, мое решение не только проще, но и является правильным способом иметь разную высоту элементов с помощью делегата. Если вы сообщаете делегату о представлении, которое его использует, и используете его методы, как и вы, это называется сильной связью и вызовет у вас проблемы на более позднем этапе вашего проекта. Поэтому я предлагаю вам использовать подход, который я описал. С таким же успехом вы можете принять ответ, так как он дает правильное решение вопроса о достижении разных высот. - person scopchanov; 30.10.2020
comment
Я согласен с вами в том, что ваше решение проще, но я не согласен с частью сильной связи, поскольку она применима только к вашему образцу. Дело в том, что у меня может быть 3 разных типа QTreeWidgetItem, и я могу добиться этого, разделив их на подклассы. Тогда у меня может быть такая логика, что подкласс моего QTreeWidget создает эти элементы. Это означает, что QTreeWidget будет полностью знать об этих типах. Как только он полностью осознает, нет ничего плохого в вызове dynamic_cast в том же исходном файле. - person aalimian; 30.10.2020
comment
@ArmaniStyles, вариант использования, который вы описываете, действительно интересен, и я был бы очень рад найти для него подходящее решение, если вы решите описать его более конкретно в другом посте. - person scopchanov; 30.10.2020