【问题标题】:Implementing QAbstractProxyModel methods实现 QAbstractProxyModel 方法
【发布时间】:2018-03-30 00:58:03
【问题描述】:

我有一个 QSqlTableModel,它的结构大致如下:

| ID |  Name  |
---------------
|  0 |  Xxxx  |
|  2 |  Yyyy  |
|  5 |  Zzzz  |

如您所见,ID(唯一的)是连续的,但可以跳过;可用范围始终为 0 到 1023。我需要创建一个表格,在不更改源模型布局的情况下填充最多 1024 行的空白。最终结果会是这样的:

|  0 |  Xxxx  |
|    |        |
|  2 |  Yyyy  |
|    |        |
|    |        |
|  5 |  Zzzz  |
      ...
|1023|  Xyz   |

“空白”项目将不可编辑,但用户将能够使用拖放来重新排序(这将在内部与 SQL 模型接口实现)和添加/删除项目,同时始终保持表大小为 1024行。

我尝试实现 QAbstractProxyModel,但遇到了一些问题。例如,不尊重标志,并且 每个 项都不可编辑,也不可选择。即使我专门返回ItemIsEnabled|ItemIsSelectable|ItemIsEditable,也没有任何变化。此外,单击项目会导致标题突出显示奇怪的结果。

我想我对 mapToSource/mapFromSource 做错了什么,但我不确定。

这是我使用 QStandardItemModel 而不是 QSqlTableModel 制作的示例(结果行为相同),有 4 行,代理显示 6 行。

class BaseModel(QtGui.QStandardItemModel):
    def __init__(self):
        QtGui.QStandardItemModel.__init__(self)
        for id, name in [(1, 'One'), (2, 'Two'), (3, 'Three'), (5, 'Five')]:
            idItem = QtGui.QStandardItem()
            idItem.setData(id, QtCore.Qt.DisplayRole)
            nameItem = QtGui.QStandardItem(name)
            self.appendRow([idItem, nameItem])


class ProxyModel(QtCore.QAbstractProxyModel):
    def data(self, index, role):
        source = self.mapToSource(index)
        if source.isValid():
            return source.data(role)
        return None

    def headerData(self, section, orientation, role):
        if orientation == QtCore.Qt.Vertical and role == QtCore.Qt.DisplayRole:
            return str(section + 1)
        return QtCore.QAbstractProxyModel.headerData(self, section, orientation, role)

    def setData(self, index, value, role):
        return self.sourceModel().setData(self.mapToSource(index), value, role)

    def index(self, row, column, parent=None):
        res = self.sourceModel().match(self.sourceModel().index(0, 0), QtCore.Qt.DisplayRole, row, flags=QtCore.Qt.MatchExactly)
        if res:
            return res[0].sibling(res[0].row(), column)
        return self.createIndex(row, column)

    def parent(self, index):
        return self.sourceModel().index(index.row(), index.column()).parent()

    def flags(self, index):
        source = self.mapToSource(index)
        if source.isValid():
            return source.flags()
        return QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEditable

    def rowCount(self, index):
        return 6

    def columnCount(self, index):
        return 2

    def mapToSource(self, index):
        res = self.sourceModel().match(self.sourceModel().index(0, 0), QtCore.Qt.DisplayRole, index.row(), flags=QtCore.Qt.MatchExactly)
        if res:
            return res[0].sibling(res[0].row(), index.column())
        return QtCore.QModelIndex()

    def mapFromSource(self, index):
        if index.row() < 0:
            return QtCore.QModelIndex()
        row = self.sourceModel().index(index.row(), 0).data()
        return self.createIndex(row, index.column())


class Win(QtWidgets.QWidget):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        layout = QtWidgets.QGridLayout()
        self.setLayout(layout)
        self.model = BaseModel()
        self.proxy = ProxyModel()
        self.proxy.setSourceModel(self.model)
        table = QtWidgets.QTableView()
        table.setModel(self.proxy)
        layout.addWidget(table)

【问题讨论】:

  • 据我了解,您希望表格显示 1024 行并填写缺失的行,对吗?如果是这样,您是否希望空行可编辑?
  • 不,空行将不可编辑,但模型将以某种方式读取/写入,因为用户可以通过内部拖放更改顺序(导致他们更改其 ID)或从外部来源“填充”空白行(通过从另一个表中删除,通过上下文菜单等)。
  • 自己解释清楚,假设从另一个表中拖出一个项目并在一个空行上释放,会发生什么?
  • 开始拖动的表格将包含没有 ID的项目;结果将是拖动的项目[s]将被添加到源表中,并根据它/它们将被放入的行为它/它们设置一个ID;我在示例中只使用了一列,实际上还有更多,其中之一是用作跨表和引用的主键的 UUID,它永远不会显示但必须在模型中。实际的数据库结构有点复杂,我简化了它,因为我认为这无关紧要。
  • 我发布了一个仅考虑您最初问题的答案,如果缺少某些内容,请告诉我。

标签: python pyqt pyqt5 qidentityproxymodel


【解决方案1】:

没有必要实现QAbstractProxyModel,在这种情况下使用QIdentityProxyModel就足够了:

class ProxyModel(QtCore.QIdentityProxyModel):
    def headerData(self, section, orientation, role):
        if orientation == QtCore.Qt.Vertical and role == QtCore.Qt.DisplayRole:
            return str(section + 1)
        return QtCore.QIdentityProxyModel.headerData(self, section, orientation, role)

    def setData(self, index, value, role=QtCore.Qt.DisplayRole):
        if index.column() == 0:
            if not (0 <= int(value) < self.rowCount()) :
                return False
        return QtCore.QIdentityProxyModel.setData(self, index, value, role)

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

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

    def mapFromSource(self, sourceIndex):
        if sourceIndex.isValid()  and 0 <= sourceIndex.row() < self.rowCount():
            ix = self.sourceModel().index(sourceIndex.row(), 0)
            return self.index(int(ix.data()), sourceIndex.column())
        return QtCore.QModelIndex()

    def mapToSource(self, proxyIndex):
        res = self.sourceModel().match(self.sourceModel().index(0, 0), QtCore.Qt.DisplayRole, proxyIndex.row(), flags=QtCore.Qt.MatchExactly)
        if res:
            return res[0].sibling(res[0].row(), proxyIndex.column())
        return QtCore.QModelIndex()

【讨论】:

  • 谢谢。从您的示例中,我意识到我犯的“唯一”大错误是通过重新映射到源模型来实现index()(我认为这就是奇怪行为的原因)。更正后,我的班级表现如预期。我考虑过使用 QIdentityProxyModel,但从描述来看,它似乎也不适合编辑源模型数据。另外,我什至可能需要一些过滤,但我想我可以使用在我的顶部使用的 QSortFilterProxyModel。无论如何,谢谢。
  • @musicamante 如果您想要过滤,则无需覆盖此代理,创建一个 QSortFilterProxyModel 并将其作为源传递给初始代理,在以下响应中有一个示例:stackoverflow.com/a/48123982/6622587
  • 是的,这就是我的意思,但我认为我会在“之后”使用它(因此是“在我的顶部”,抱歉,我没有正确解释自己):SqlModel > MyProxy > 过滤代理。由于它可能会过滤掉“空白”行,所以应该会更好,还是有什么我没有考虑的?
  • @musicamante 如果您不想过滤空行,请覆盖 QSortProxyModel 的 filterAcceptsRow 方法以接受这些行。
  • @musicamante 是的,如果您意识到您使用的转换不是可交换的,那就不同了。
猜你喜欢
  • 1970-01-01
  • 2014-09-07
  • 2017-05-17
  • 2022-01-16
  • 2020-04-08
  • 2016-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多