如何在PySide中创建一个代理模型,将QAbstractItemModel的节点展平为列表?

10

我有一个由自定义QAbstractItemModel表示的节点层级。是否可能创建一个代理模型,将层级展平为列表,以便在QListView中呈现所有节点/项(如果没有代理,则只呈现树的第一级)?

A                           A
+---1                       1
    2                       2
    +--3                    3
    4            =>         4
B                           B
+---5                       5
    6                       6
    +--7                    7
       8                    8

谢谢,FipS


我会从创建同一模型的新视图类型的角度来处理这个问题。即不要为模型创建代理,而是创建一种展示树形结构扁平化表示的新视图类型。 - Oliver
做一个“新类型的视图”(如果你指的是一个新的QAbstractItemView子类)的正确实现并不比这样的代理简单... - Frank Osterfeld
@Schollii 我同意Frank Osterfeld的观点。不仅视图“并不比代理更简单”,而且视图要复杂得多。只需查看Qt模型和视图的源代码即可了解。与视图相比,模型看起来非常幼稚 :) - Kuba hasn't forgotten Monica
2
@kubaober,你的关于使用QTreeView的回答是我所说的完美例子! :) - Oliver
1个回答

12

把一个 QTreeView 强制变成一个列表视图要容易得多:

view = QtGui.QTreeView()
view.setModel(model)
view.expandAll()
view.setIndentation(0)
view.header().hide() 

如果您真的想做到这一点,代理并不是最简单的事情,因为它需要保留源模型的结构模型。对于一个改变结构的源模型,代理也必须跟踪源模型的结构。

以下是一个具有静态结构模型的最小实现起点。我只在Python 3.3上进行了测试。更改在视图之间传播-您可以在任何一个视图中编辑项目的文本,底层树形模型将被修改,并适当地通知其他视图。

由于列表模型已经是扁平的,因此代理应该简单地通过列表模型。为了证明这种透明度,右窗格是一个代理的列表视图,附加到中间窗格中查看的代理。中间窗格中查看的代理附加到左窗格中查看的树形模型。

screenshot

我非常乐意接受那些真正懂Python / PySide的人的编辑。目前,我对Python的了解非常娱乐。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
from PySide import QtCore, QtGui

class FlatProxyModel(QtGui.QAbstractProxyModel):
    @QtCore.Slot(QtCore.QModelIndex, QtCore.QModelIndex)
    def sourceDataChanged(self, topLeft, bottomRight):
        self.dataChanged.emit(self.mapFromSource(topLeft), \
                              self.mapFromSource(bottomRight))
    def buildMap(self, model, parent = QtCore.QModelIndex(), row = 0):
        if row == 0:
            self.m_rowMap = {}
            self.m_indexMap = {}
        rows = model.rowCount(parent)
        for r in range(rows):
            index = model.index(r, 0, parent)
            print('row', row, 'item', model.data(index))
            self.m_rowMap[index] = row
            self.m_indexMap[row] = index
            row = row + 1
            if model.hasChildren(index):
                row = self.buildMap(model, index, row)
        return row
    def setSourceModel(self, model):
        QtGui.QAbstractProxyModel.setSourceModel(self, model)
        self.buildMap(model)
        print(flush = True)
        model.dataChanged.connect(self.sourceDataChanged)
    def mapFromSource(self, index):
        if index not in self.m_rowMap: return QtCore.QModelIndex()
        #print('mapping to row', self.m_rowMap[index], flush = True)
        return self.createIndex(self.m_rowMap[index], index.column())
    def mapToSource(self, index):
        if not index.isValid() or index.row() not in self.m_indexMap:
            return QtCore.QModelIndex()
        #print('mapping from row', index.row(), flush = True)
        return self.m_indexMap[index.row()]
    def columnCount(self, parent):
        return QtGui.QAbstractProxyModel.sourceModel(self)\
               .columnCount(self.mapToSource(parent))
    def rowCount(self, parent):
        #print('rows:', len(self.m_rowMap), flush=True)
        return len(self.m_rowMap) if not parent.isValid() else 0
    def index(self, row, column, parent):
        #print('index for:', row, column, flush=True)
        if parent.isValid(): return QtCore.QModelIndex()
        return self.createIndex(row, column)
    def parent(self, index):
        return QtCore.QModelIndex()
    def __init__(self, parent = None):
        super(FlatProxyModel, self).__init__(parent)

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)

    model = QtGui.QStandardItemModel()
    names = ['Foo', 'Bar', 'Baz']
    for first in names:
        row = QtGui.QStandardItem(first)
        for second in names:
            row.appendRow(QtGui.QStandardItem(first+second))
        model.appendRow(row)

    proxy = FlatProxyModel()
    proxy.setSourceModel(model)

    nestedProxy = FlatProxyModel()
    nestedProxy.setSourceModel(proxy)

    w = QtGui.QWidget()
    layout = QtGui.QHBoxLayout(w)
    view = QtGui.QTreeView()
    view.setModel(model)
    view.expandAll()
    view.header().hide()
    layout.addWidget(view)
    view = QtGui.QListView()
    view.setModel(proxy)
    layout.addWidget(view)
    view = QtGui.QListView()
    view.setModel(nestedProxy)
    layout.addWidget(view)
    w.show()

    sys.exit(app.exec_())

两种解决方案都完美地运作了,谢谢!我之前不知道 expandAll() + setIndentation(0) 的灵活性,但是现在也很高兴知道如何为此创建代理...感谢您的时间。FipS - FipS
@Kuba没有忘记Monica:您有没有关于如何将平面表转换为树形结构的提示,其中层次结构由0(平面)到N个列确定。我知道这在其他UI库的视图中作为功能出现(例如,在此处https://devexpress.github.io/devextreme-reactive/react/grid/docs/guides/grouping/),但是对于Qt,我只找到了一些半成品解决方案,建议使用代理并调整mapTo / FromSource。由于这具有陡峭的学习曲线,我感激任何帮助。此外,我认为它是一个超级实用的组件“GroupByColumnsProxyModel”。 - tobilocker

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