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

1k Views Asked by At

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.

  • If I subclass filterAcceptsColumn in the proxy to return True only for the second column,, then no rows are shown. I believe this is because the parent/child relationships are anchored on the first column in the indexes, and when the proxy ask for the number of rows for a given index of column 1, the model returns 0 (which is the expected behavior in the model implementation if I understood well).
  • If I set rowCount to return non 0 values in the model for columns index > 0, I can see the tree and the rows, but then the model is not passing the QAbstractItemModelTester test with the folloing error:
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 !

1

There are 1 best solutions below

0
musicamante On BEST ANSWER

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_())