qtpyqtqtreeviewqabstractitemmodelqsortfilterproxymodel

How to hide the first column using a QSortFilterProxyModel with a Qabstractitemmodel


In widget my implementation, model the source model (subclassed from a QAbstractItemModel), proxy_model (subclassed from QSortFilterProxyModel) the proxy, and tree (QTreeView) the tree representing the model.

I want to hide the first column. I tried to use tree.hideColumn(0), the the tree is shown flat.

qt.modeltest: FAIL! childIndex != childIndex1 () returned FALSE

I understand well that in the tree model, child index must be attached to a single parent index (the first column). But how am I supposed to be hiding the first column in a proxy model if the parent child relationship of the source model are not retained by the proxy if the first column is filtered? I feel it is a "bug" from the proxy, or I missed something !

Do anyone know the proper way of filtering/hiding the first column in the tree view without losing the parent/child information, and still validating a qmodel implementation?

Thanks !


Solution

  • A proper and correct implementation would at least require the proxy to create indexes for the parent of the second column, requiring correct implementation of index(), parent(), mapToSource() and mapFromSource(). For tree models that can be really tricky.

    If the source model is not too complex and all its functions are correctly implemented, a possible workaround could be to just override the data() (and headerData) of the proxy and always return the sibling of the next column.

    The following test is done with a simple QStandardItemModel, but I don't think using a QAbstractItemModel should be any different, as long as it's correctly implemented.

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    class ColumnSwapProxy(QtCore.QSortFilterProxyModel):
        def data(self, index, role=QtCore.Qt.DisplayRole):
            return super().data(index.sibling(index.row(), index.column() + 1), role)
    
        def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
            if orientation == QtCore.Qt.Horizontal:
                section += 1
            return super().headerData(section, orientation, role)
    
    class Test(QtWidgets.QWidget):
        def __init__(self):
            super().__init__()
            layout = QtWidgets.QVBoxLayout(self)
    
            self.combo = QtWidgets.QComboBox()
            layout.addWidget(self.combo)
    
            self.tree = QtWidgets.QTreeView()
            layout.addWidget(self.tree)
            self.model = QtGui.QStandardItemModel()
            self.createTree(self.model.invisibleRootItem())
    
            self.tree.setModel(self.model)
            self.model.setHorizontalHeaderLabels(
                ['Root', 'Fake root'] + ['Col {}'.format(c) for c in range(2, 6)])
            self.tree.header().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
    
            self.proxy = ColumnSwapProxy()
            self.proxy.setSourceModel(self.model)
    
            self.combo.addItem('Base model', self.model)
            self.combo.addItem('First column hidden', self.proxy)
            self.combo.currentIndexChanged.connect(self.setModel)
    
        def setModel(self):
            model = self.combo.currentData()
            self.tree.setModel(model)
            lastColumn = self.model.columnCount() - 1
            self.tree.header().setSectionHidden(lastColumn, model == self.proxy)
    
        def createTree(self, parent, level=0):
            for r in range(10):
                first = QtGui.QStandardItem('Root {} (level {})'.format(level + 1, r + 1))
                if level < 2 and not r & 3:
                    self.createTree(first, level + 1)
                row = [first]
                for c in range(5):
                    row.append(QtGui.QStandardItem(
                        'Column {} (level {})'.format(c + 2, level + 1)))
                parent.appendRow(row)
    
    
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = Test()
    w.show()
    sys.exit(app.exec_())