PyQt5 模型/视图 - 一个模型,两个视图

Posted

技术标签:

【中文标题】PyQt5 模型/视图 - 一个模型,两个视图【英文标题】:PyQt5 Model/View - One model, two views 【发布时间】:2020-04-27 18:26:25 【问题描述】:

我正在使用 PyQt5 设计一个应用程序。我需要在两个表上显示相同的数据,但在每个表上以不同的方式显示。那是: 第一个表是可编辑的。可以编辑几个元素(它们的名称、值等)

然而,第二个表格需要在垂直标题上显示第一个表格的第一列(元素名称),并将第一个表格的其中一列(在本例中为第三个)作为唯一的行:

(我还没有做到这一点,我画了它以便更好地理解)

为了使两个表之间的数据保持一致(并且在内部,因为表中的值用于其他操作),我认为使用模型/架构是最好的方法。我已经开始为第一个表实现模型(继承 QAbstractTableModel),但是这个模型类中的方法(数据、行计数、列计数...)对于每个表来说应该是非常不同的。

我应该如何解决这个问题?我应该为第二个表创建一个自定义 View 类吗?

【问题讨论】:

【参考方案1】:

如果您想获得基于另一个模型的新模型,那么您可以使用 QXProxyModel。在您的特定情况下,您可以使用以下过程转换模型:

使用 QTransposeProxyModel 旋转表格。 使用 hideRow() 隐藏行。 通过实现基于 QIdentityProxyModel 的代理来映射标头。
from PyQt5 import QtCore, QtGui, QtWidgets


class CustomProxyModel(QtCore.QIdentityProxyModel):
    def headerData(self, section, orientation, role):
        if role == QtCore.Qt.DisplayRole:
            if orientation == QtCore.Qt.Horizontal:
                return self.sourceModel().index(0, section).data(role)
            else:
                return 1
        return super().headerData(section, orientation, role)

    def setSourceModel(self, model):
        super().setSourceModel(model)
        model.dataChanged.connect(self._on_headerDataChanged)

    def _on_headerDataChanged(self, topLeft, bottomRight, roles):
        if topLeft.row() <= 0 <= bottomRight.row():
            self.headerDataChanged.emit(
                QtCore.Qt.Horizontal, topLeft.row(), bottomRight.row()
            )


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    model = QtGui.QStandardItemModel(3, 4)

    datas = (
        ("Element 1", "2", "3", "4"),
        ("Another element", "6", "7", "8"),
        ("Element nr. 3", "10", "11", "12"),
    )

    for i, r in enumerate(datas):
        for j, d in enumerate(r):
            it = QtGui.QStandardItem(d)
            model.setItem(i, j, it)

    proxy = QtCore.QTransposeProxyModel()
    proxy.setSourceModel(model)

    proxy2 = CustomProxyModel()
    proxy2.setSourceModel(proxy)

    view1 = QtWidgets.QTableView()
    view1.setModel(model)

    view2 = QtWidgets.QTableView()
    view2.setModel(proxy2)

    for r in range(proxy.rowCount()):
        if r != 2:
            view2.hideRow(r)

    w = QtWidgets.QWidget()
    lay = QtWidgets.QHBoxLayout(w)
    lay.addWidget(view1)
    lay.addWidget(view2)
    w.resize(640, 480)
    w.show()

    sys.exit(app.exec_())

【讨论】:

感谢您的回答。尽管最终的实现会比我预期的更复杂,但您的回答将非常有帮助。我有一个问题:在 CustomProxyModel > _on_headerDataChanged 中,发射信号的参数不应该是 topLeft.column(), bottomRight.column() 吗? (而不是 row(),因为这是来自转置模型...@eyllanesc @kru96 它给您带来了麻烦吗? dataChanged信号是模型的典型,即不考虑是否代理。 首先,很抱歉这么长时间才回复...不,我还没有尝试过。我只是在查看您的代码并试图理解它之后问它。一旦我尝试了这一切我会写一个回复,但目前我能够在这个项目上投入很少的时间。【参考方案2】:

您的设计中缺少的部分是完整的模型视图-视图模型流的观察者设计模式实现。 您可以实现自己的可观察数据容器以绑定到多个视图here 或者您可以使用 pypi 的观察者 3rd 方库。我建议实现您自己的可观察数据容器,以更好地管理未来的更改,例如像您的问题中那样转置表格视图。

【讨论】:

以上是关于PyQt5 模型/视图 - 一个模型,两个视图的主要内容,如果未能解决你的问题,请参考以下文章

两个视图模型之间的淘汰赛传递值

一个模型,两个不同的视图 - PySide

在主干视图中显示两个模型?

mvc一个视图能不能使用两个模型类

一个视图中的两个模型,仅验证其中一个

片段的视图模型