如何使用带有 Qabstractitemmodel 的 QSortFilterProxyModel 隐藏第一列
How to hide the first column using a QSortFilterProxyModel with a Qabstractitemmodel
在小部件我的实现中,model
源模型(从 QAbstractItemModel
继承),proxy_model
(从 QSortFilterProxyModel 继承)代理,tree
(QTreeView ) 代表模型的树。
我想隐藏第一列。
我尝试使用 tree.hideColumn(0)
,树显示为平坦的。
- 如果我在代理中将
filterAcceptsColumn
子类化为 return 仅对第二列为 True,则不会显示任何行。
我相信这是因为 parent/child 关系锚定在索引的第一列上,当代理询问第 1 列给定索引的行数时,模型 returns 0 (如果我理解得好,这是模型实现中的预期行为。
- 如果我将模型中列索引 > 0 的
rowCount
设置为 return 非 0 值,我可以看到树和行,但是模型没有通过 QAbstractItemModelTester
测试出现以下错误:
qt.modeltest: FAIL! childIndex != childIndex1 () returned FALSE
我很清楚在树模型中,子索引必须附加到单个父索引(第一列)。
但是,如果第一列被过滤,如果源模型的父子关系没有被代理保留,我应该如何隐藏代理模型中的第一列?我觉得这是代理的“错误”,或者我错过了什么!
有谁知道 filtering/hiding 树视图中第一列的正确方法而不丢失 parent/child 信息,并且仍然验证 qmodel 实现?
谢谢!
正确且正确的实现至少需要代理为 second 列的父级创建索引,需要正确实现 index()
、parent()
、mapToSource()
和 mapFromSource()
。对于可能非常棘手的树模型。
如果源模型不是太复杂并且其所有功能都已正确实现,可能的解决方法是覆盖代理的 data()
(和 headerData
)并始终 return 下一列的兄弟。
以下测试是使用简单的 QStandardItemModel 完成的,但我不认为使用 QAbstractItemModel 应该有任何不同,只要它正确实现即可。
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_())
在小部件我的实现中,model
源模型(从 QAbstractItemModel
继承),proxy_model
(从 QSortFilterProxyModel 继承)代理,tree
(QTreeView ) 代表模型的树。
我想隐藏第一列。
我尝试使用 tree.hideColumn(0)
,树显示为平坦的。
- 如果我在代理中将
filterAcceptsColumn
子类化为 return 仅对第二列为 True,则不会显示任何行。 我相信这是因为 parent/child 关系锚定在索引的第一列上,当代理询问第 1 列给定索引的行数时,模型 returns 0 (如果我理解得好,这是模型实现中的预期行为。 - 如果我将模型中列索引 > 0 的
rowCount
设置为 return 非 0 值,我可以看到树和行,但是模型没有通过QAbstractItemModelTester
测试出现以下错误:
qt.modeltest: FAIL! childIndex != childIndex1 () returned FALSE
我很清楚在树模型中,子索引必须附加到单个父索引(第一列)。 但是,如果第一列被过滤,如果源模型的父子关系没有被代理保留,我应该如何隐藏代理模型中的第一列?我觉得这是代理的“错误”,或者我错过了什么!
有谁知道 filtering/hiding 树视图中第一列的正确方法而不丢失 parent/child 信息,并且仍然验证 qmodel 实现?
谢谢!
正确且正确的实现至少需要代理为 second 列的父级创建索引,需要正确实现 index()
、parent()
、mapToSource()
和 mapFromSource()
。对于可能非常棘手的树模型。
如果源模型不是太复杂并且其所有功能都已正确实现,可能的解决方法是覆盖代理的 data()
(和 headerData
)并始终 return 下一列的兄弟。
以下测试是使用简单的 QStandardItemModel 完成的,但我不认为使用 QAbstractItemModel 应该有任何不同,只要它正确实现即可。
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_())