Skip to content
Advertisement

How to keep QWidgets in QTreeWidget hidden during resize?

I have a simple UI with QWidgets within a QTreeWidget. Some of them need to be hidden. Every time the application is resized, all QWidgets become visible. Below is a minimal example to demonstrate the issue. How can I make sure the QWidgets stay hidden until setVisible(True) is called explicitly? pyqt version is 5.12.3

from PyQt5.QtWidgets import QApplication, QWidget, QTreeWidgetItem, QTreeWidget, QCheckBox, QPushButton, QVBoxLayout
import sys

class Foo(QWidget):

    def __init__(self):   
        super().__init__() 
        lay = QVBoxLayout(self)        
        tree = QTreeWidget()
        
        item = QTreeWidgetItem()
        tree.addTopLevelItem(item)
        self.checkBox = QCheckBox()
        tree.setItemWidget(item,0,self.checkBox)        
        
        button = QPushButton('Hide Checkbox')
        button.clicked.connect(self.hideCheckbox)
        
        lay.addWidget(tree)
        lay.addWidget(button)
        lay.addWidget(button)
    
    def hideCheckbox(self):
        self.checkBox.setVisible(False)  # is reset on resize
    
if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = Foo()
    w.show()
    sys.exit(app.exec_())

Advertisement

Answer

Considering that this is caused by a call to the private updateEditorGeometries() function of item views, which automatically shows index widgets whenever the view updates the geometry of its contents, there’s no direct control over that.

There are two possible workarounds, though.

Add the item widget to a container

In this case, the item widget is a basic QWidget that acts as a container for the actual widget. Since the show() will only be called on the item widget, explicitly hiding the child widgets will avoid the problem:

    self.checkBox = QCheckBox()
    container = QWidget()
    containerLayout = QHBoxLayout(container)
    containerLayout.setContentsMargins(0, 0, 0, 0)
    containerLayout.addWidget(self.checkBox)
    tree.setItemWidget(item, 0, container)

Note that it’s important to set the contents margins to 0 (otherwise the default Qt layout margins would be added).

Override updateGeometries() in a QTreeWidget subclass

updateGeometries() is called by item views whenever they are resized or the contents of the viewport require internal changes (columns/rows are resized, the model has changed, etc).

You could create a subclass of QTreeWidget, keep an internal list of widgets set for items, and then keep track of the hidden ones before calling the base implementation:

class TreeWidget(QTreeWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.itemWidgets = set()

    def setItemWidget(self, item, column, widget):
        self.itemWidgets.add(widget)
        widget.destroyed.connect(lambda: self.itemWidgets.discard(widget))
        super().setItemWidget(item, column, widget)

    def updateGeometries(self):
        hidden = [w for w in self.itemWidgets if w.isHidden()]
        super().updateGeometries()
        for widget in hidden:
            widget.setVisible(False)
User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement