kru96
kru96

Reputation: 391

PyQt5 Model/View - One model, two views

I'm designing an application using PyQt5. I need to display the same data on two tables, but in a different way on each table. That is: First table is editable. Several elements can be edited (Their names, values etc.)

enter image description here

Second table, however, needs to display the first column of the first table (element name) on the vertical headers and one of the columns of the first table (third one, in this example) as the only row:

(I haven't achieved this, i drew it for better understanding)

enter image description here

For the Data to be consistent between the two tables (and internally, since the values in the tables are used for other operations) I think using a Model/Architecture is the best way to go. I have started implementing the Model (inheriting QAbstractTableModel) for the first table, but the methods in this Model Class (data, rowCount, columnCount, ...) should be very different for each table.

How should I approach this problem? Should I create a custom View class for the second table?

Upvotes: 2

Views: 1099

Answers (2)

eyllanesc
eyllanesc

Reputation: 243973

If you want to get a new model based on another model then you can use the QXProxyModel. In your particular case you can transform the model using the following procedure:

  • Rotate the table using a QTransposeProxyModel.
  • Hide the rows with hideRow().
  • Map the header by implementing a proxy based on 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_())

enter image description here

Upvotes: 2

oetzi
oetzi

Reputation: 1052

missing part in your design is observer design pattern implementation for a full-fledged modelview-view model flow. you may implement your own observable data container to be binded to multiple views as here or you may use a observer 3rd party library from pypi. I suggest to implement your own observable data container to better manage future changes such as transposing the table view like in your question.

Upvotes: 1

Related Questions