QTreeView 和 QTableView 的 Qt 模型

Posted

技术标签:

【中文标题】QTreeView 和 QTableView 的 Qt 模型【英文标题】:Qt Model for both QTreeView and QTableView 【发布时间】:2021-02-24 17:48:27 【问题描述】:

我正在尝试创建一个可用于 QTableView 和 QTreeView 的模型。例如,我的数据类似于:

ID Location Name
101 201 Apple
201 None Kitchen
102 201 Banana
301 None Cellar
302 301 Potatoes
202 302 Nail

所以每个条目都有一个位置,该位置本身就是模型中的一个条目。对于 QTableView,我想简单地显示所有条目,如上所示,而对于 QTreeView,我想要类似的东西

201:厨房 101:苹果 102:香蕉 301:地窖 302:土豆 202:钉子

然而,我的问题是我无法弄清楚如何实现 QAbstractProxyModel.maptoSource() 或 mapfromSource(),因为我丢失了 QTableView 中有关父级的信息。阅读https://www.qtcentre.org/threads/26163-Map-table-to-tree-through-model-view-possible 似乎这根本不可能。然而 QAbstractProxyModel 明确表示这是为了在两个视图中显示数据。谁能指出我正确的方向或知道是否可以实现这样的模型?特别是在 Python 中,很遗憾我找不到任何示例。


我真的很喜欢将未缩进的 TreeView 用作一种 TableView 的想法。不幸的是,我仍然无法创建模型。目前,仅显示顶部条目。

class MyModel(qtg.QStandardItemModel):
    def __init__(
        self,
        engine
    ):
        self.engine = engine
        self.hierarchy_key = 'location_id'

        
        self.column_names = ['id', 'location_id', 'name', 'quantity']
        super().__init__(0, len(self.fields))

        self.setHorizontalHeaderLabels(self.column_names)
        self.root = self.invisibleRootItem()
        self.build_model()

    def build_model(self):
        def add_children_to_tree(entries, parent_item):
            for entry in entries:
                items = []
                for col in self.column_names:
                    text = getattr(entry, col)
                    item = qtg.QStandardItem(text)
                    items.append(qtg.QStandardItem(text))

                parent_item.appendRow(items)
                item = items[1] #the location_id item
                parent_item.setChild(item.index().row(), item.index().column(), item)

                with session_scope(self.engine) as session:
                    child_entries = (
                        session.query(self.entry_type)
                            .filter(
                            getattr(getattr(self.entry_type, self.hierarchy_key), "is_")(
                                entry.id
                            )
                        )
                            .all()
                    )
                    if child_entries:
                        add_children_to_tree(child_entries, item)

        self.removeRows(0, self.rowCount())
        with session_scope(self.engine) as session:
            root_entries = session.query(self.entry_type).filter(getattr(getattr(self.entry_type, self.hierarchy_key), "is_")(None)).all()
            if not isinstance(root_entries, list):
               root_entries = [root_entries]
            add_children_to_tree(root_entries, self.root)

这个想法是会话查询会产生一个条目列表。每个条目都是数据库中的一条记录,具有“id”、“location_id”等属性。因此,每个属性都是一个项目,项目列表在模型中创建一行。我无法弄清楚如何以此处显示的方式使该行项目成为另一行的子项: 我假设 setChild() 函数需要以不同的方式调用?

【问题讨论】:

“我在 QTableView 中丢失了有关父级的信息” 这没有多大意义。该视图不应该为您提供信息。该信息来自您正在代理的源模型。制作代理时,忘记视图 - 模型不关心视图,它们必须在没有附加视图的情况下工作!我对您的问题到底是什么感到有些不知所措:这根本不难做到。请发布一些代码,否则如果没有人为你做这一切,这将无法解决。 在任何情况下,您都不需要做任何特别的事情来显示一棵树“好像”它是一张桌子。只需告诉树视图将自身展平(查找)。从字面上看是一行代码,尽管表格格式与您显示的不完全一样 - 它只是一棵扁平的树。将所有三列都放在树中,然后在显示为树时隐藏其中一列,或者在显示为伪表时全部显示。 看看this answer 是否有用:它至少可以满足您的部分需求。 我第一部分的措辞是错误的——我的意思是如果我使用代理模型来展平我的标准模型,我会丢失信息。但我喜欢把树弄平的想法——不知道这是可能的!不幸的是,我在 Python 中找不到任何具有二维子级的树的示例。 【参考方案1】:

由于明显缺乏 python 示例,我将在此处发布我修改过的 simpletreemodel 版本,这最终对我有用。然后按照建议使用 QTreeView 而不是 QTableView ,我让表的行为也像我想要的那样。总体而言,这将创建 MyItem,它是一个包含整行信息的项目,然后我使用递归将子级添加到父级,如果它们的 hierarchy_key (location_id) 的值等于父级的 id。


class MyItem(object):
    def __init__(self, data, parent=None):
        self.parentItem = parent
        self.itemData = data
        self.childItems = []

    def appendChild(self, item):
        self.childItems.append(item)

    def child(self, row):
        return self.childItems[row]

    def childCount(self):
        return len(self.childItems)

    def columnCount(self):
        return len(self.itemData)

    def data(self, column=None):
        try:
            if column == None:
                return [self.itemData[i] for i in range(self.columnCount())]
            return self.itemData[column]
        except IndexError:
            return None

    def parent(self):
        return self.parentItem

    def row(self):
        if self.parentItem:
            return self.parentItem.childItems.index(self)
        return 0


class MyModel(QtCore.QAbstractItemModel):
    def __init__(self, entry_type, engine, hierarchy_key, description_key, parent=None):
        super(ORMModel, self).__init__(parent)

        self.entry_type = entry_type
        if isinstance(self.entry_type, str):
            self.entry_type = getattr(ds, self.entry_type)
        self.engine = engine
        self.hierarchy_key = hierarchy_key
        
        self.column_names = ['id', 'location_id', 'name', 'quantity']

        self.rootItem = MyItem(self.column_names)
        self.setHeaderData(0, Qt.Horizontal, self.rootItem)
        self.initiateModel()

    def root(self):
        return self.rootItem

    def columnCount(self, parent):
        if parent.isValid():
            return parent.internalPointer().columnCount()
        else:
            return self.rootItem.columnCount()

    def data(self, index, role):
        if not index.isValid():
            return None
        item = index.internalPointer()

        if role == Qt.DisplayRole:
            return item.data(index.column())

        return None

    def flags(self, index):
        if not index.isValid():
            return QtCore.Qt.NoItemFlags

        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable

    def headerData(self, section, orientation, role):
        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
            return self.rootItem.data(section)

        return None

    def index(self, row, column, parent):
        if not self.hasIndex(row, column, parent):
            return QtCore.QModelIndex()

        if not parent.isValid():
            parentItem = self.rootItem
        else:
            parentItem = parent.internalPointer()

        childItem = parentItem.child(row)
        if childItem:
            return self.createIndex(row, column, childItem)
        else:
            return QtCore.QModelIndex()

    def parent(self, index):
        if not index.isValid():
            return QtCore.QModelIndex()

        childItem = index.internalPointer()
        parentItem = childItem.parent()

        if parentItem == self.rootItem:
            return QtCore.QModelIndex()

        return self.createIndex(parentItem.row(), 0, parentItem)

    def rowCount(self, parent):
        if parent.column() > 0:
            return 0

        if not parent.isValid():
            parentItem = self.rootItem
        else:
            parentItem = parent.internalPointer()

        return parentItem.childCount()

    def initiateModel(self):
        def add_children_to_tree(entries, parent_item):
            for entry in entries:
                row = []
                for field in self.fields.keys():
                    val = getattr(entry, field)
                    if isinstance(val, list):
                        text = "; ".join(map(str, val))
                    else:
                        text = str(val)
                    row.append(text)
                    item = ORMItem(row, parent_item)
                parent_item.appendChild(item)

                with session_scope(self.engine) as session:
                    child_entries = (
                        session.query(self.entry_type)
                        .filter(
                            getattr(
                                getattr(self.entry_type, self.hierarchy_key), "is_"
                            )(entry.id)
                        )
                        .all()
                    )
                    if child_entries:
                        add_children_to_tree(child_entries, item)

        with session_scope(self.engine) as session:
            root_entries = (
                session.query(self.entry_type)
                .filter(
                    getattr(getattr(self.entry_type, self.hierarchy_key), "is_")(None)
                )
                .all()
            )
            if not isinstance(root_entries, list):
                root_entries = [root_entries]
            add_children_to_tree(root_entries, self.rootItem)

【讨论】:

以上是关于QTreeView 和 QTableView 的 Qt 模型的主要内容,如果未能解决你的问题,请参考以下文章

如何将带有列标题的 QTreeView 重做为 QTableView?

如何在 QTableView 列中添加 QTreeView

Qt窗口及控件-QTreeview/QTableView排序问题

QTreeView/QTableView中利用QStandardItem实现复选框三种形态变化

Qt:setAlternatingRowColors,QTableView隔行自动变色

QTreeView/QTableView中利用QStandardItem实现复选框三种形态变化