How can I programmatically expand/collapse and select an item in a tree?

Viewed 769

Let's say I added a TreeView widget called treeView in Qt Designer and I'm using this code to add a few items to it:

class StandardItem(Qt.QStandardItem):
    def __init__(self, txt='', font_size=11, set_bold=False, color=Qt.QColor(0, 0, 0)):
        super().__init__()
        fnt = Qt.QFont('Open Sans', font_size)
        fnt.setBold(set_bold)
        self.setEditable(False)
        self.setForeground(color)
        self.setFont(fnt)
        self.setText(txt)


model = Qt.QStandardItemModel()
rootNode = model.invisibleRootItem()
A = StandardItem("A")
A.appendRows([StandardItem("1"),StandardItem("2"),StandardItem("3")])
B = StandardItem("B")
B.appendRows([StandardItem("1"),StandardItem("2")])
rootNode.appendRows([A,B])
self.treeView.setModel(model)

How can I write a function that collapses/expands item A? And how can I write a function that selects item A, as if it has been clicked? I might be missing something in the docs.

1 Answers

Expansion and selection are tasks that affect the visual part so they must be handled by sight. The first task is done through the setExpanded() method and the second using the select() method of the selectionModel() associated with the view, in both cases the QModelIndex associated with the item is used:

import sys
from PyQt5 import Qt


class StandardItem(Qt.QStandardItem):
    def __init__(self, txt="", font_size=11, set_bold=False, color=Qt.QColor(0, 0, 0)):
        super().__init__()
        fnt = Qt.QFont("Open Sans", font_size)
        fnt.setBold(set_bold)
        self.setEditable(False)
        self.setForeground(color)
        self.setFont(fnt)
        self.setText(txt)


class MainWindow(Qt.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.treeView = Qt.QTreeView()
        self.setCentralWidget(self.treeView)

        self.model = Qt.QStandardItemModel()
        rootNode = self.model.invisibleRootItem()
        A = StandardItem("A")
        A.appendRows([StandardItem("1"), StandardItem("2"), StandardItem("3")])
        B = StandardItem("B")
        B.appendRows([StandardItem("1"), StandardItem("2")])
        rootNode.appendRows([A, B])

        self.treeView.setModel(self.model)

        index_A = A.index()

        self.treeView.setExpanded(index_A, True)
        self.treeView.selectionModel().select(index_A, Qt.QItemSelectionModel.Select)


def main():
    app = Qt.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    app.exec_()


if __name__ == "__main__":
    main()

Note:

It seems that the index() function of the QStandardItem has a bug since it sometimes returns invalid QModelIndex for valid QStandardItem.

print(A.child(0).index().isValid())

Output:

False

This is caused because when the child was added, the "A" was not part of the model then the model is null in those children, as can be seen with.

print(A.child(0).model())

Output:

None

If instead "A" are added to the model first and then the children are newly added, then the model is passed.

self.model = Qt.QStandardItemModel()
rootNode = self.model.invisibleRootItem()
A = StandardItem("A")
B = StandardItem("B")
rootNode.appendRows([A, B])

A.appendRows([StandardItem("1"), StandardItem("2"), StandardItem("3")])
B.appendRows([StandardItem("1"), StandardItem("2")])

print(A.child(0).model())
print(A.child(0).index().isValid())

Output:

<PyQt5.QtGui.QStandardItemModel object at 0x7fb40aa868b0>
True

In that case it is better to use the indexFromItem() method of QStandardItemModel:

self.treeView.selectionModel().select(
    self.model.indexFromItem(A.child(0)), Qt.QItemSelectionModel.Select
)
Related