简体   繁体   English

Qt 和 Python - QIdentityProxyModel 在嵌套在 QSortFilterProxyModel 顶部时没有获得正确的列数

[英]Qt and Python - QIdentityProxyModel does not get the right column count when nested on top of a QSortFilterProxyModel

I have three models, a QAbstractItemModel "sourceModel", QSortFilterProxyModel "proxyModel" for filtering, and a QIdentityProxyModel "dataModel" for modifying columns and displayed data.我有三个模型,一个 QAbstractItemModel “sourceModel”,一个用于过滤的 QSortFilterProxyModel “proxyModel”,一个用于修改列和显示数据的 QIdentityProxyModel “dataModel”。

When I only layer QAbstractItemModel -> QIdentityProxyModel, it all works well.当我只分层 QAbstractItemModel -> QIdentityProxyModel 时,一切正常。 My source model only has one default column given, I can add multiple new columns on my QIdentifyProxyModel, and my view displays all the right data.我的源 model 只给出了一个默认列,我可以在 QIdentifyProxyModel 上添加多个新列,并且我的视图显示所有正确的数据。 This allows me to reuse the same model on multiple views with different data, great.这使我可以在具有不同数据的多个视图上重用相同的 model,非常棒。

However, if I layer QAbstractItemModel -> QSortFilterProxyModel -> QIdentityProxyModel, then my view only shows data in the first column, and the selection model breaks.但是,如果我将 QAbstractItemModel -> QSortFilterProxyModel -> QIdentityProxyModel 分层,那么我的视图仅在第一列中显示数据,并且选择 model 会中断。 If I define an equal or higher amount of columns in my source model, then all the QIdentityProxyModel columns behaves correctly in my view, and shows different data than what I defined in my source model.如果我在源 model 中定义了相等或更多数量的列,那么所有 QIdentityProxyModel 列在我的视图中都会正确运行,并且显示的数据与我在源 model 中定义的数据不同。

When proxies are nested, the QIdentityProxyModel class can still access the source item with the data, but the index passed to the QIdentityProxyModel's data-function only queries the amount columns defined in the source model.当代理嵌套时,QIdentityProxyModel class 仍然可以使用数据访问源项目,但传递给 QIdentityProxyModel 的数据函数的索引仅查询源 model 中定义的数量列。

Any idea how to go about this?知道如何 go 关于这个吗? Any help is much appreciated!任何帮助深表感谢!

from PySide2 import QtCore, QtWidgets, QtGui

class Item(dict):
    def __init__(self, data=None):
        super(Item, self).__init__()

        self._children = list()
        self._parent = None
        if data:
            self.update(data)

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

    def child(self, row):
        if row >= len(self._children):
            return
        return self._children[row]

    def children(self):
        return self._children

    def parent(self):
        return self._parent

    def row(self):
        if self._parent is not None:
            siblings = self.parent().children()
            return siblings.index(self)

    def add_child(self, child):
        child._parent = self
        self._children.append(child)

class TreeModel(QtCore.QAbstractItemModel):
    Columns = list()
    ItemRole = QtCore.Qt.UserRole + 1

    def __init__(self, parent=None):
        super(TreeModel, self).__init__(parent)
        self._root_item = Item()

    def rowCount(self, parent):
        if parent.isValid():
            item = parent.internalPointer()
        else:
            item = self._root_item
        return item.childCount()

    def columnCount(self, parent):
        return len(self.Columns)

    def setColumns(self, keys):
        assert isinstance(keys, (list, tuple))
        self.Columns = keys

    def data(self, index, role):
        if not index.isValid():
            return None
        if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
            item = index.internalPointer()
            column = index.column()

            key = self.Columns[column]
            return item.get(key, None)

        if role == self.ItemRole:
            return index.internalPointer()

    def headerData(self, section, orientation, role):
        if role == QtCore.Qt.DisplayRole:
            if section < len(self.Columns):
                return self.Columns[section]
        super(TreeModel, self).headerData(section, orientation, role)


    def parent(self, index):
        item = index.internalPointer()
        parent_item = item.parent()

        if parent_item == self._root_item or not parent_item:
            return QtCore.QModelIndex()

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

    def index(self, row, column, parent):
        if not parent.isValid():
            parent_item = self._root_item
        else:
            parent_item = parent.internalPointer()

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

    def add_child(self, item, parent=None):
        if parent is None:
            parent = self._root_item
        parent.add_child(item)

    def column_name(self, column):
        if column < len(self.Columns):
            return self.Columns[column]

    def clear(self):
        self.beginResetModel()
        self._root_item = Item()
        self.endResetModel()


class CustomSortFilterProxyModel(QtCore.QSortFilterProxyModel):
    def __init__(self, parent=None):
        super(CustomSortFilterProxyModel, self).__init__(parent)

    def filterAcceptsRow(self, row, parent):
        model = self.sourceModel()
        index = model.index(row, self.filterKeyColumn(), parent)
        item = index.internalPointer()
        if item.get('name'):
            return True
        else:
            return False


class IdentityProxyModel(QtCore.QIdentityProxyModel):
    def __init__(self, *args, **kwargs):
        super(IdentityProxyModel, self).__init__(*args, **kwargs)
        self.Columns = []
        self._root_item = Item()

    def setColumns(self, keys):
        assert isinstance(keys, (list, tuple))
        self.Columns = keys
    #
    def columnCount(self, parent):
        # return 3
        return len(self.Columns)

    def column_name(self, column):
        if column < len(self.Columns):
            return self.Columns[column]

    def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
        if role == QtCore.Qt.DisplayRole:
            if section < len(self.Columns):
                return self.Columns[section]
        super(IdentityProxyModel, self).headerData(section, orientation, role)

    def data(self, index, role):
        if not index.isValid():
            return
        if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
            item = self.mapToSource(index).data(TreeModel.ItemRole)
            column = index.column()

            key = self.Columns[column]
            return item.get(key, None)

        return super(IdentityProxyModel, self).data(index, role)


if __name__ == '__main__':
    import sys

    sourceModel = TreeModel()
    sourceModel.setColumns(['name'])
    sourceModel.add_child(Item({'name': 'itemA', 'number': '1', 'info': 'A'}))
    sourceModel.add_child(Item({'name': 'itemB', 'number': '2', 'info': 'B'}))

    proxyModel = CustomSortFilterProxyModel()
    proxyModel.setSourceModel(sourceModel)

    dataModel = IdentityProxyModel()
    dataModel.setSourceModel(proxyModel)
    dataModel.setColumns(['name', 'info'])

    app = QtWidgets.QApplication(sys.argv)
    view = QtWidgets.QTreeView()
    view.setModel(dataModel)
    view.show()
    sys.exit(app.exec_())

Tree models are tricky.树模型很棘手。 A lot .很多

Whenever you have to deal with them, you have to really understand recursion.每当您必须处理它们时,您都必须真正了解递归。 And with Qt models (which are somehow complex) that becomes even harder, and often a cause of massive headaches and late nights.而对于 Qt 模型(有些复杂),这变得更加困难,并且通常是导致严重头痛和熬夜的原因。

I am basing the following code on a previous answer of mine which is very basic and doesn't consider the possibility of having inconsistent number of child columns.我将以下代码基于我以前的答案,这是非常基本的,并且不考虑子列数量不一致的可能性。

The concept is that proxy models that are based on tree structures need to keep track of the hierarchy (which is usually based on the parent index internal pointer or id), and that must be considered whenever "ghost" columns are created.这个概念是基于树结构的代理模型需要跟踪层次结构(通常基于父索引内部指针或 id),并且在创建“ghost”列时必须考虑这一点。

All of the above is always very important.以上所有内容始终非常重要。 Item views need all of the implementation in order to properly update their contents and allow valid user interaction.项目视图需要所有实现才能正确更新其内容并允许有效的用户交互。

class IdentityProxyModel(QtCore.QIdentityProxyModel):
    def __init__(self, *args, **kwargs):
        super(IdentityProxyModel, self).__init__(*args, **kwargs)
        self.Columns = []
        self._parents = {}
        self._root_item = Item()

    # ...
    def _isInvalid(self, column):
        # we assume that the model always has the same column count
        return column > self.sourceModel().columnCount() - 1

    def mapToSource(self, index):
        if self._isInvalid(index.column()):
            index = index.sibling(index.row(), 0)
        return super().mapToSource(index)

    def index(self, row, column, parent=QtCore.QModelIndex()):
        if self._isInvalid(column):
            index = self.createIndex(row, column, parent.internalId())
            self._parents[index] = parent
            return index
        return super().index(row, column, parent)

    def parent(self, index):
        if self._isInvalid(index.column()):
            return self._parents[index]
        return super().parent(index)

    def flags(self, index):
        if self._isInvalid(index.column()):
            return self.flags(index.sibling(index.row(), 0))
        return super().flags(index)

    def sibling(self, row, column, idx):
        if self._isInvalid(column):
            return self.index(row, column, idx.parent())
        elif self._isInvalid(idx.column()):
            idx = self.index(idx.row(), 0, idx.parent())
        return super().sibling(row, column, idx)

Note: overrides should always use the signature of their base implementation.注意:覆盖应始终使用其基本实现的签名。 For instance, both rowCount() and columnCount() must accept an invalid keyword parent argument, which is not used for "fast calls" on the top level of the model.例如, rowCount()columnCount()必须接受无效的关键字父参数,该参数用于 model 顶层的“快速调用”。 The common practice is to use a basic QModelIndex instance, since it is fundamentally an immutable object (but None is commonly accepted too):通常的做法是使用一个基本的 QModelIndex 实例,因为它基本上是一个不可变的 object (但None也被普遍接受):

    def rowCount(self, parent=QtCore.QModelIndex()):
        # ...

    def columnCount(self, parent=QtCore.QModelIndex()):
        # ...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM