pythonqtpyqt5qtreewidgetqtreewidgetitem

How to resize a QTreeWidgetItem when their widget is changed?


I have made a custom QTreeWidget class to make a kind of collapsible list of widgets. Now when I change the widget in one of the section, let's say from a short widget to a taller one, the size does not update to respect the taller widget's size.

BUT if i collapse and then re-expand that item, it resizes it correctly.

import sys
from PyQt5.QtWidgets import (QToolButton, QTreeWidgetItem, QLabel, QFrame, QSizePolicy,
                             QTreeWidget, QVBoxLayout, QApplication, QComboBox)


class PanelButton(QToolButton):
    def __init__(self, item: QTreeWidgetItem, text):
        super().__init__()
        self.item = item
        self.setText(text)
        self.setCheckable(True)
        self.clicked.connect(self.on_clicked)

    def on_clicked(self, checked):
        if self.item.isExpanded():
            self.item.setExpanded(False)
        else:
            self.item.setExpanded(True)


class CustomTreeWidget(QTreeWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setHeaderHidden(True)
        self.setIndentation(0)

    def add_section(self, widget, title):
        widget.setParent(self)
        button_item = QTreeWidgetItem()
        self.addTopLevelItem(button_item)
        button = PanelButton(button_item, text=title)
        self.setItemWidget(button_item, 0, button)
        body_item = QTreeWidgetItem(button_item)
        self.setItemWidget(body_item, 0, widget)
        button_item.addChild(body_item)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    tree_widget = CustomTreeWidget()

    section1 = QFrame()
    layout = QVBoxLayout(section1)
    spin_box = QComboBox()
    spin_box.addItems(["short", "tall"])
    short_widget = QLabel("Short widget")

    tall_widget = QLabel("There should be more text visible under:\n\n\nmore text")
    layout.addWidget(spin_box)

    layout.addWidget(tall_widget)
    layout.addWidget(short_widget)

    size_policy = QSizePolicy()
    size_policy.setVerticalPolicy(QSizePolicy.Policy.Minimum)
    size_policy.setHorizontalPolicy(QSizePolicy.Policy.Preferred)
    size_policy.setVerticalStretch(0)
    size_policy.setHorizontalStretch(0)
    short_widget.setSizePolicy(size_policy)
    tall_widget.setSizePolicy(size_policy)
    short_widget.show()
    tall_widget.hide()

    def change_widget(index):
        short_widget.setVisible(index == 0)
        tall_widget.setVisible(index == 1)
        # None of these worked:
        # tree_widget.adjustSize()
        # tall_widget.adjustSize()
        # short_widget.adjustSize()
        # tree_widget.adjustSize()
        # tree_widget.updateGeometries()
        # tree_widget.updateGeometry()

    spin_box.currentIndexChanged.connect(change_widget)
    tree_widget.add_section(section1, "Section1")
    tree_widget.add_section(QLabel("Some other section"), "Section2")

    tree_widget.show()
    sys.exit(app.exec_())

I've tried:


Solution

  • Size hints are normally cached, especially when used for widgets set in item views. What could actually always change is the minimum size or minimum size hint of the contained widget, but if that is unchanged (or, better, smaller than the size hint), they are just ignored for performance reasons.

    What could be done is to properly update the size hint of the item based on the contents of the widgets, and then call updateGeometries().

    In order to do that, at least based on your example, is to keep a reference to the item in which the container widget was set; then update the size hint for that item based on that of the container and, finally, call updateGeometries():

    class CustomTreeWidget(QTreeWidget):
        ...
        def add_section(self, widget, title):
            ...
            return body_item
    
    ...
    if __name__ == "__main__":
        ...
        def change_widget(index):
            short_widget.setVisible(index == 0)
            tall_widget.setVisible(index == 1)
            body_item.setSizeHint(0, section1.sizeHint())
            tree_widget.updateGeometries()
    

    Obviously, a more appropriate approach would use a subclass for the whole container with proper functions that would both set the visibility of nested items and emit a custom signal for the size hint change, then connect that signal in the main window when the container is added which would eventually call updateGeometries().