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