Search code examples
pythonpyside2qtreeviewqtreewidget

QTreeView dynamic height according to content, disclosure triangle


I want to emulate a disclosure triangle / disclosure widget with QTreeWidget. For that, I created a QTreeWidget with only one top level item. Its background is transparent, its header is invisible.

Collapsed Expanded

Here is the code (mwe):

import sys
from PySide2.QtWidgets import ( QApplication,
                                QLabel,
                                QTreeWidget,
                                QTreeWidgetItem, 
                                QVBoxLayout,
                                QWidget)


class DisclosureTree(QTreeWidget):

    def __init__(self):

        super().__init__()

        # Add content
        self.setColumnCount(1)
        parent_item = QTreeWidgetItem(self, ['Disclosure triangle'])
        self.addTopLevelItem(parent_item)
        parent_item.addChild(QTreeWidgetItem(parent_item, 'AAA'))
        parent_item.addChild(QTreeWidgetItem(parent_item, 'BBB'))
        parent_item.addChild(QTreeWidgetItem(parent_item, 'CCC'))

        # Cosmetics
        self.header().hide()
        self.setStyleSheet('background-color: transparent;')

if __name__ == '__main__':
    app = QApplication()

    vbox = QVBoxLayout()
    disclosuretree = DisclosureTree()
    label = QLabel('Plenty of space above…')
    vbox.addWidget(disclosuretree)
    vbox.addWidget(label)
    vbox.addStretch()

    widget = QWidget()
    widget.setLayout(vbox)
    widget.show()

    sys.exit(app.exec_())

The problem is that I want the widget height to automatically resize whether it is collapsed or expanded, like here for example:

enter image description here

Does anybody know how to do that? Thanks a lot and let me know if I can help!


Solution

  • You have to update the maximum height of the widget, based on its contents. For each top item you need to get the height for that row using rowHeight() and do the same recursively for child items whenever the item is expanded. Also, you need to overwrite the sizeHint and minimumSizeHint.

    class DisclosureTree(QTreeWidget):
        def __init__(self):
            # ...
            self.expanded.connect(self.updateHeight)
            self.collapsed.connect(self.updateHeight)
    
        def updateHeight(self):
            self.setMaximumHeight(self.sizeHint().height())
    
        def getHeight(self, parent=None):
            height = 0
            if not parent:
                parent = self.rootIndex()
            for row in range(self.model().rowCount(parent)):
                child = self.model().index(row, 0, parent)
                height += self.rowHeight(child)
                if self.isExpanded(child) and self.model().hasChildren(child):
                        height += self.getHeight(child)
            return height
    
        def sizeHint(self):
            hint = super().sizeHint()
            hint.setHeight(self.getHeight() + self.frameWidth() * 2)
            return hint
    
        def minimumSizeHint(self):
            return self.sizeHint()