Search code examples
c++qtqwidgetqtreewidgetqtreewidgetitem

How to have QTreeWidgetItems of different heights in a QTreeWidget utilizing QStyledItemDelegate?


NOTE: it turned out that the problem was not due to the implementation of QStyledItemDelegate, but it was the fact that in the constructor of MyTreeWidget I was calling setUniformRowHeights(true). The code below and the solution posted by @scopchanov are valid and working

QTreeWidget has a protected method called itemFromIndex() and this is how I am making it accessible:

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

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

In my QStyledItemDelegate, I am storing a pointer to MyTreeWidget and then overriding its virtual sizeHint() method and based on the type of the QTreeWidgetItem adding a padding.

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

This doesn't work, since sizeHint() of the delegate doesn't get called for every single QTreeWidgetItem.

So my text options to call setSizeHint() in the constructor of MyCustomTreeWidgetItem1, and that didn't seem to have any effect either. Is Qt ignoring it because there is a delegate?

Another option was to set a minimum height of a QWidget that is contained in MyCustomTreeWidgetItem which is made possible via the QTreeWidget::setItemWidget().

So it looks like the moment I use the delegate, I am confined to only size. Is my option to get rid of the delegate or there's something else I can try?

I know many people would say switch from a QTreeWidget to a QTreeView, but it's not possible at the moment.


Solution

  • Solution

    I would approach this problem in a different (simpler) manner:

    1. Define an enumeration for the different item sizes, e.g.:

       enum ItemType : int {
           IT_ItemWithRegularPadding,
           IT_ItemWithBigPadding
       };
      
    2. When creating an item, set the desired size in its user data, depending on its type, e.g.:

       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. In the reimplementation of sizeHint retrieve the desired size from the index's data, e.g.:

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

    Example

    Here is an example I wrote for you to demonstrate how the proposed solution could be implemented:

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

    Note: This example sets the item's size depending on its index - odd or even. Feel free to change this by implementing the logic you need to differentiate between the items.

    Result

    The given example produces the following result:

    Tree widget with items of different sizes

    The even and odd items are of a different height.