在QTreeView中显示QAbstractTableModel的数据

3

简短版

我正在尝试在 QTreeView 中显示来自 QAbstractTableModel 的数据。但是,当我这样做时,我得到的是每个节点(以及子节点和孙子节点等)的整个表格作为其子项。如何显示抽象表模型的树形视图?

详细版

我正在尝试在 QTreeView 中显示一些来自 QAbstractTableModel 的数据。在 Model-View Tutorial 中,它提供了一个示例 QAbstractTableModel,并让人觉得只需将 QTableView 替换为 QTreeView 即可:

您可以将上面的示例转换为具有树形视图的应用程序。只需将 QTableView 替换为 QTreeView,就会得到一个读/写树。不需要对模型进行任何更改。

当我进行这个替换时,会出现一个显示树,但如果我点击任何图标来展开它(因为里面没有建立层级关系),Python 会崩溃并显示“Python.exe 已停止工作”。这个问题已经被提出过,但没有可行的解决方案。
为了尝试修复这个行为,我重新实现了我的QAbstractTableModel子类中的索引函数(请参见下面的完整工作示例)。这导致了一种非常不同类型的错误。也就是说,现在每个节点在数据上都包含整个表。无论我点击多少次,整个表格都会显示出来。像这样:

bad tree

我似乎陷入了某种递归噩梦中,不知道如何逃脱。下面的相关问题建议我可能需要转到 QAbstractItemModel,但上面的教程引用则表明相反(它指出:模型不需要进行任何更改)。

相关问题

QTreeView 总是显示相同的数据

完整工作示例

from PySide import QtGui, QtCore

class Food(object):
    def __init__(self, name, shortDescription, note, parent = None):
        self.data = (name, shortDescription, note);
        self.parentIndex = parent

class FavoritesTableModel(QtCore.QAbstractTableModel):
    def __init__(self):
        QtCore.QAbstractTableModel.__init__(self)
        self.foods = []  
        self.loadData() 

    def data(self, index, role = QtCore.Qt.DisplayRole):
        if role == QtCore.Qt.DisplayRole:
            return self.foods[index.row()].data[index.column()]
        return None

    def rowCount(self, index=QtCore.QModelIndex()):
        return len(self.foods)

    def columnCount(self, index=QtCore.QModelIndex()):
        return 3

    def index(self, row, column, parent = QtCore.QModelIndex()):  
        return self.createIndex(row, column, parent)

    def loadData(self):   
        allFoods=("Apples", "Pears", "Grapes", "Cookies", "Stinkberries")
        allDescs = ("Red", "Green", "Purple", "Yummy", "Huh?")
        allNotes = ("Bought recently", "Kind of delicious", "Weird wine grapes",
                    "So good...eat with milk", "Don't put in your nose")
        for name, shortDescription, note in zip(allFoods, allDescs, allNotes):
            food = Food(name, shortDescription, note)                                      
            self.foods.append(food) 

def main():
    import sys
    app = QtGui.QApplication(sys.argv)

    model = FavoritesTableModel() 

    #Table view
    view1 = QtGui.QTableView()
    view1.setModel(model)
    view1.show()

    #Tree view
    view2 = QtGui.QTreeView()
    view2.setModel(model)
    view2.show()

    sys.exit(app.exec_())

if __name__ == '__main__':
    main()
2个回答

5
官方文档中可以看到:
当实现基于表的模型时,当父项有效时,rowCount()应返回0。同样适用于columnCount()。为了完整起见,如果父项有效,则data()应返回None
发生的情况如下:
1.您单击“Stinkberries”旁边的“+”号。 2. QTreeView认为:“我需要展开视图。我想知道'Stinkberries'下有多少行?”为了找出答案,QTreeView调用rowCount(),并将“Stinkberries”单元格的索引作为父级传递。 3. FavoritesTableModel::rowCount()返回5,因此QTreeView认为:“啊,'Stinkberries'下有5行。” 4.列的过程相同。 5. QTreeView决定检索“Stinkberries”下的第一项。它调用data(),传递Row 0、Column 0和“Stinkberries”单元格的索引作为父项。 6. FavoritesTableModel::data()返回“Apples”,因此QTreeView认为:“啊,'Stinkberries'下的第一项是'Apples'。”等等。
为了获得正确的行为,您的代码必须在步骤#3和#4中返回0。
最后,为了确保“+”符号根本不出现,请让hasChildren()对于每个单元格都返回false。

我添加了 def hasChildren(self, index): return False 但仍然出现加号。 - eric
抱歉,我在想C++。我没有PySide(甚至Python)的经验,但希望PySide的行为与原始的C++行为相匹配。 - JKSH
1
如果父项有效,我肯定是指返回0(或None)。原因是:所有顶级项目都没有父项;只有嵌套树项目具有有效的父项。表格模型不包含嵌套项,因此您模型中的所有项目都应该没有父项。(请澄清:您理解我的编号列表的哪些部分,哪些部分不理解?) - JKSH
1
我注意到你声明了 def rowCount(self, index=QtCore.QModelIndex())。为什么你把参数命名为“index”?实际上,该参数是指父项。(请参见 http://pyside.github.io/docs/pyside/PySide/QtCore/QAbstractItemModel.html#PySide.QtCore.PySide.QtCore.QAbstractItemModel.rowCount )无论如何,正确的代码应该是 def rowCount(self, parent=QtCore.QModelIndex()): if (parent != None) return 0 ... - JKSH
有趣的是:def rowCount(self, parent=QtCore.QModelIndex()): if (parent != QtCore.QModelIndex()): return 0; else: return len(self.foods) 看起来很有帮助:当我这样做时,加号消失了,这表明我在每个节点下面没有更多的隐藏数据(耶!)。似乎 hasChildren 没有被调用——当我在其中添加一个打印语句时,它从未被执行……我将继续研究这个问题,仍然有点困惑如何最好地实现“索引”,以及我的食品类是否足够好,还有很多其他的东西。明天会更新。 - eric
显示剩余2条评论

4
解决方案 理论上,模型-视图框架的一个很好的特点是可以有多个相同模型的视图。但实际上,QAbstractTableModel 真正的作用是帮助您查看表格,而不是树形结构。对于 QAbstractTableModel 的文档表示:

由于该模型提供比 QAbstractItemModel 更专业的接口,因此它不适用于树视图。

然而,即使有这样的警告,也有一种方法可以让它工作。首先,如 JKSH 指出的那样,您必须修复 rowCount(请注意,它的第二个参数是一个 索引):
def rowCount(self, parent=QtCore.QModelIndex()):
    if parent.isValid():
        return 0
    return len(self.foods)

其次,删除“index”的异想天开的重新实现,这使得选择行为变得非常奇怪,原因我坦白说不太理解。
总体而言,如果您想要一个通用模型,那么最好从“QAbstractItemModel”派生子类,而不是使用预定义的模型。
讨论
在表格模型中,忽略rowCount中的parent参数是可以接受的。在官方的Qt书籍中,他们遵循标准程序,将rowCount仅返回表格中要显示的行数。Blanchette和Summerfield指出:
“对于表格模型,父参数没有意义;它存在是因为rowCount()和columnCount()是继承自更通用的QAbstractItemModel基类,该基类支持层次结构。(p 255)”
在他的PyQt书中,Summerfield指出:
“[T]he parent QModelIndex p matters only to tree models (p 434)”
基本上,rowCount告诉您有多少行要显示在父项下面。因为在表格中所有项都有相同的父项,所以父项在QTableView中不使用。但正如JKSH在他的回答中很好地指出的原因,这种策略在树形结构中行不通。
因此,声称父参数“对于表格模型没有意义”的说法应该修改为:只有当数据仅由QTableView显示时才是真的(这通常是一个非常好的假设)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接