1

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.

santahopar
  • 2,933
  • 2
  • 29
  • 50

1 Answers1

1

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.

scopchanov
  • 7,966
  • 10
  • 40
  • 68
  • I was able to get to the bottom of my issue, the problem was that my `QTreeWidget` was setting `uniformRowHeights` property to true. Taking it off solved the problem. – santahopar Oct 30 '20 at 02:05
  • Your solution however eliminates the need for `dynamic_cast`, so I upvoted it. – santahopar Oct 30 '20 at 02:07
  • @ArmaniStyles, my solution is not only simpler, but it is the proper way to have different items' heights by using a delegate. If you let the delegate know about the view which uses it and use its methods, as you do, this is called _tight coupling_ and would cause you troubles at a later stage of your project. So I suggest you to use the approach I've described. You might as well accept the answer, as it gives a valid solution to the question about achieving different heights. – scopchanov Oct 30 '20 at 02:16
  • I do agree with you that your solution is simpler, but I don't agree with the tight coupling part, since it only applies to your sample. The fact of the matter is that I may have 3 different types of `QTreeWidgetItem` and achieve it by sub-classing them. Then I may have logic such that a sub-class of my `QTreeWidget` is creating those items. This means that the `QTreeWidget` will be aware of those types fully. Once it is aware fully there's no harm in calling `dynamic_cast` in the same source file. – santahopar Oct 30 '20 at 18:16
  • @ArmaniStyles, the use case you describe is indeed interesting and I would be very glad to look for a proper solution for it, if you decide to describe it more specifically in another post. – scopchanov Oct 30 '20 at 19:41