Buzz
Buzz

Reputation: 1412

Show branch of treemodel in QComboBox

Disclaimer: I found different similar answers for this topic, but I couldn't get the one that solves mine.

I have the following source model structure that is shown by a single-column, headerless QTreeView:

- Cat 1
|-- Cat1_El1
|-- ...
|-- Cat1_ElL
...
- Cat N
|-- CatN_El1
|-- ...
|-- CatN_ElM

The view and the source model work fine. Each low-level item is dynamically created by the user, while the categories are hard-coded.

Now, the core issue follows: Based upon a certain condition, that I can easily manage through an extra property of an instanziated Proxy model, I'd like to display in a QComboBox all the items of a specific category. I thought of re-implementing the QAbstractProxyModel but I was facing some troubles to understand how to re-implement the two methods mapFromSource and mapToSource stated by the Qt Doc. What I came up with is shown below. All my doubts and inferring are reported as comments, as I thought it clearer. Note that I was coding in Python, using PyQt5, but actually I'm much more interested into the concept of how to re-implement the proxy rather than into the code itself (thus, also a C++ code works very fine)

class GWModulesComboProxy(QIdentityProxyModel):
    # This re-implements the basic proxy structure
    def __init__(self, moduleType : CategoriesType, parent: Optional[QObject]) -> None:
        # `CategoriesType` --> See later `ComboProxyCatN`
        self.moduleType = moduleType
        super().__init__(parent)

    def index(self, row: int, column: int, parent: QModelIndex) -> QModelIndex:
    #   Based upon the crash of the model when clicking on the combobox, I'm inferring that I should re-implementing 
    #   this method and call, FROM it, the `mapToSource`. But I can't figure out how
        pass

    def mapFromSource(self, sourceIndex: QModelIndex) -> QModelIndex:
        if not sourceIndex.isValid():
            return QModelIndex()
        
        # If the node is a root node (i.e. a Cathegory itself), it will never show up into the ComboBox
        if not sourceIndex.parent().isValid():
            return QModelIndex()
        
        # Into the source model, the root nodes are ordered against the IntEnum CategoriesType --> See later `ComboProxyCatN`
        if sourceIndex.parent().row() == self.moduleType:
            return sourceIndex
        
        return QModelIndex()

    def mapToSource(self, proxyIndex: QModelIndex) -> QModelIndex:
        if not proxyIndex.isValid():
            return QModelIndex()

        parent = self.sourceModel().index(self.moduleType, 1, QModelIndex()) 
        return self.sourceModel().index(proxyIndex.row(), proxyIndex.column(), parent)


        
    def data(self, proxyIndex: QModelIndex, role: int) -> Any:
    #   I was guessing that re-implementing `data` was meaningless since the data should be already taken from the source
    #       by means of the mapper methods. Am I correct?
        pass

    def columnCount(self, parent: QModelIndex) -> int:
        return 1

class ComboProxyCatN(ComboProxy):
    # Just an example how to manage the specific Category N
    def __init__(self, parent: Optional[QObject]) -> None:
        # CategoriesType is an IntEnum that stores the different cathegories
        super().__init__(CategoriesType.CatN, parent)

Upvotes: 0

Views: 216

Answers (1)

eyllanesc
eyllanesc

Reputation: 243965

To show a branch of the tree it is not necessary to use a proxymodel but rather the root must be indicated through the setRootModelIndex() method of the QComboBox.

The following example shows the branch of the category "Cat 5":

import random

from PyQt5 import QtCore, QtGui, QtWidgets


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    model = QtGui.QStandardItemModel()

    for i in range(1, 10):
        root_item = QtGui.QStandardItem(f"Cat {i}")
        model.appendRow(root_item)
        for j in range(1, random.randrange(5, 10)):
            child_item = QtGui.QStandardItem(f"Cat{i}_El{j}")
            root_item.appendRow(child_item)

    view = QtWidgets.QTreeView()
    view.setModel(model)
    view.expandAll()

    category = "Cat 5"

    root_index = QtCore.QModelIndex()
    indexes = model.match(
        model.index(0, 0), QtCore.Qt.DisplayRole, category, flags=QtCore.Qt.MatchExactly
    )
    if indexes:
        root_index = indexes[0]

    # or fifth row
    # root_index = model.index(4, 0)

    combobox = QtWidgets.QComboBox()
    combobox.setModel(model)
    combobox.setRootModelIndex(root_index)
    combobox.setCurrentIndex(0)

    widget = QtWidgets.QWidget()
    lay = QtWidgets.QVBoxLayout(widget)
    lay.addWidget(view)
    lay.addWidget(combobox)
    widget.resize(640, 480)
    widget.show()

    sys.exit(app.exec_())

Upvotes: 3

Related Questions