如何使用带有 Qabstractitemmodel 的 QSortFilterProxyModel 隐藏第一列

Posted

技术标签:

【中文标题】如何使用带有 Qabstractitemmodel 的 QSortFilterProxyModel 隐藏第一列【英文标题】:How to hide the first column using a QSortFilterProxyModel with a Qabstractitemmodel 【发布时间】:2021-07-18 02:14:04 【问题描述】:

在小部件我的实现中,model 源模型(从 QAbstractItemModel 子类化),proxy_model(从 QSortFilterProxyModel 子类化)代理,tree (QTreeView) 代表模型的树。

我想隐藏第一列。 我尝试使用tree.hideColumn(0),树显示为平面。

如果我在代理中将filterAcceptsColumn 子类化为仅对第二列返回True,则不会显示任何行。 我相信这是因为父/子关系锚定在索引的第一列上,当代理询问第 1 列的给定索引的行数时,模型返回 0(这是如果我理解得很好,模型实现)。 如果我将rowCount 设置为在模型中为列索引> 0 返回非0 值,我可以看到树和行,但是模型没有通过QAbstractItemModelTester 测试并出现以下错误:
qt.modeltest: FAIL! childIndex != childIndex1 () returned FALSE

我很清楚在树模型中,子索引必须附加到单个父索引(第一列)。 但是,如果源模型的父子关系在第一列被过滤的情况下未被代理保留,我应该如何隐藏代理模型中的第一列?我觉得这是代理的“错误”,或者我错过了什么!

有谁知道过滤/隐藏树视图中第一列而不丢失父/子信息并仍然验证 qmodel 实现的正确方法?

谢谢!

【问题讨论】:

嗯,这有点棘手。问题在于,正如您已经发现的那样,树模型(至少在 Qt 中)只考虑每个父索引的第一列的子项。如果您考虑这些子项,问题会变得更加复杂:模型是否应该为它们隐藏第一列?正确的实现可能会变得非常困难(并且可能有问题),所以我建议尝试“破解”你的方式:因为我假设你只是为了显示目的才需要它,如果你只是返回每个***项目的column+1数据? 感谢您的回答!我不确定你提出的 hack 会有什么不同..? 这个想法不是隐藏第一列,而是在请求的列旁边显示该列的数据,并最终隐藏最后一列。这样就保持了父子关系(由于模型结构始终相同,索引始终引用原始列),唯一的区别是显示的内容移动了一列。 如果我做得好,那么你的树视图中就会出现一个空列,对吧? 嗯,理论上是的,但是你可以隐藏 that 最后一列,而不用担心结构。请注意,我没有测试过它,这只是一个突然出现在我脑海中的想法,但对我来说似乎很有意义。 【参考方案1】:

正确和正确的实现至少需要代理为 second 列的父级创建索引,需要正确实现 index()parent()mapToSource() 和 @987654324 @。对于可能非常棘手的树模型。

如果源模型不太复杂并且其所有功能都已正确实现,则可能的解决方法是仅覆盖代理的data()(和headerData)并始终返回下一列的兄弟。

以下测试是使用简单的 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_())

【讨论】:

以上是关于如何使用带有 Qabstractitemmodel 的 QSortFilterProxyModel 隐藏第一列的主要内容,如果未能解决你的问题,请参考以下文章

如何在 QAbstractItemModel 和 QTreeView 类中找到子项?

如何在 QAbstractItemModel 中为 QTreeView 创建人工节点

QAbstractItemModel data() 永远不会被调用

以编程方式检查QAbstractItemModel / QTreeView中的项目

QTreeView 的 QAbstractItemModel:我做错了啥?

QAbstractItemModel - QModelIndex 对象在创建时是不是应该被缓存?