Search code examples
pythonsearchpyqt6qtreewidget

PyQt6: Find element by path in QTreewidget


How to select this element in Qtreewidget using the path to an element?

The path is written as an example string: parent/parent/parent/parent/element

It is important to search for elements along the entire path and not only by the name of this element, because there may be many elements in the tree with the same name, but each one will certainly have a different path.

I tried to solve it in ten ways:

def find_and_select_item(self):
    path = self.comboBox_Scene.currentText()
    path_elements = path.split('/')
    current_item = self.treeWidget.invisibleRootItem()
    for element in path_elements:
        items = self.treeWidget.findItems(element, Qt.MatchFlag, 0)
        if items:
            current_item = items[0]
        else:
            # Element not found in the current level of the tree
            return None

    # Select the found item
    current_item.setSelected(True)
    return current_item

Unfortunately, this function returns an error::
TypeError: findItems(self, text: str, flags: Qt.MatchFlag, column: int = 0): argument 2 has unexpected type 'EnumType'


Solution

  • This is most easily implemented using recursion, which can provide a more general solution that is capable of finding multiple matching pathways in the tree (if necessary).

    Below is a basic working demo that shows how to achieve this. Since the question states that each element has a different path, this demo only looks for the first matching item. However, the searchByPath method returns a generator that can iterate over all matches if necessary (and it can also start searching from any point in the tree).

    enter image description here

    from PyQt6 import QtCore, QtGui, QtWidgets
    
    class Window(QtWidgets.QWidget):
        def __init__(self):
            super().__init__()
            self.button = QtWidgets.QPushButton('Test')
            self.button.clicked.connect(self.handleButton)
            self.edit = QtWidgets.QLineEdit()
            self.tree = QtWidgets.QTreeWidget()
            self.tree.setHeaderHidden(True)
            layout = QtWidgets.QVBoxLayout(self)
            layout.addWidget(self.tree)
            layout.addWidget(self.edit)
            layout.addWidget(self.button)
            for text in ('Red', 'Blue', 'Green'):
                level0 = QtWidgets.QTreeWidgetItem(self.tree, [text])
                for text in ('Cyan', 'Violet', 'Yellow'):
                    level1 = QtWidgets.QTreeWidgetItem(level0, [text])
                    for text in ('Orange', 'Brown', 'Purple'):
                        level2 = QtWidgets.QTreeWidgetItem(level1, [text])
                        for text in ('Gold', 'Silver', 'Bronze'):
                            level3 = QtWidgets.QTreeWidgetItem(level2, [text])
            self.edit.setText('Green/Violet/Orange/Bronze')
    
        def handleButton(self):
            self.tree.clearSelection()
            self.tree.collapseAll()
            path = self.edit.text()
            if path:
                item = next(self.searchByPath(path), None)
                if item is not None:
                    self.tree.scrollToItem(item)
                    item.setSelected(True)
    
        def searchByPath(self, path, root=None):
            if root is None:
                root = self.tree.invisibleRootItem()
            elements = list(map(str.strip, path.split('/')))
            last = len(elements) - 1
            def search(parent, level=0):
                if level <= last:
                    target = elements[level]
                    for index in range(parent.childCount()):
                        child = parent.child(index)
                        if child.text(0) != target:
                            continue
                        if level == last:
                            yield child
                        else:
                            yield from search(child, level + 1)
            return search(root)
    
    
    if __name__ == '__main__':
    
        app = QtWidgets.QApplication(['Test'])
        window = Window()
        window.setGeometry(600, 100, 300, 300)
        window.show()
        app.exec()