QAbstractItemModel:创建 table 个具有映射子元素的父元素

QAbstractItemModel: create a table of parent elements with mapped children

我需要制作一个 table,其中每个项目都是父项,有两个子项(两个参数,映射到 lineEdit 'param 1' 和'param 2)。 table 中的父值应该是参数 1 和参数 2 的乘积。结果也映射到 3d lineEdit:

我以前使用过 QAbstractTableModel,但由于它不支持父子,所以我需要继承 QAbstractItemModel。

请帮助重写我的代码以实现我的目标

test.ui 文件:https://dropmefiles.com/JqSIy

代码:

from PyQt5 import QtWidgets, QtCore, QtGui, uic
import sys

class Model(QtCore.QAbstractItemModel):

    def __init__(self, data_list, h_headers, v_headers, parent = None):
        super(Model, self).__init__()
        self.data_list = data_list
        self.h_headers = h_headers
        self.v_headers = v_headers
        self.parent = parent

    def rowCount(self, parent):
        return len(self.v_headers)

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

    def data(self, index, role):

        if role == QtCore.Qt.DisplayRole:
            row = index.row()
            column = index.column()
            value = self.data_list[row][column]
            return value

    def setData(self, index, value, role = QtCore.Qt.EditRole):

        if role == QtCore.Qt.EditRole:
            column = index.column()
            row = index.row()
            self.data_list[row][column] = value
            self.dataChanged.emit(index, index)
            return True

        return False

    def headerData(self, section, orientation, role):

        if role == QtCore.Qt.DisplayRole:
            if orientation == QtCore.Qt.Horizontal:
                return self.h_headers[section]
            if orientation == QtCore.Qt.Vertical:
                return self.v_headers[section]

    def flags(self, index):
        return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled

    
    def index(self, row, column, parent):
        pass
    

def test_setup(w):
    list =          [
                    [1,2,3],
                    [4,5,6],
                    [7,8,9]
                    ]
    h_headers = ['h1', 'h2', 'h3']
    v_headers = ['v1', 'v2', 'v3']
    w.model = Model(list, h_headers, v_headers)
    w.tableView.setModel(w.model)

    w.widget_mapper = QtWidgets.QDataWidgetMapper()

    w.tableView.clicked.connect(lambda: add_mapping(w))

def add_mapping(w):
    row = w.tableView.selectionModel().selectedIndexes()[0].row()
    column = w.tableView.selectionModel().selectedIndexes()[0].column()
    w.widget_mapper.setModel(w.model)
    w.widget_mapper.addMapping(w.lineEdit, column)
    w.widget_mapper.setCurrentIndex(row)


app = QtWidgets.QApplication(sys.argv)

test = uic.loadUi("test.ui")
test_setup(test)
test.show()

sys.exit(app.exec())

模型不必是树类型,因为具有角色的 table 类型模型就足够了。也不应该使用QDataWidgetMapper,因为使用它需要一定的结构,在这种情况下显然不满足,所以解决方案是实现数据更新逻辑。

from dataclasses import dataclass, field
import random

from PyQt5 import QtCore, QtGui, QtWidgets


Param1Role = QtCore.Qt.UserRole
Param2Role = QtCore.Qt.UserRole + 1
ResultRole = QtCore.Qt.UserRole + 2


@dataclass
class Item:
    param1: float
    param2: float
    result: float = field(init=False)

    def __post_init__(self):
        self.recalculate()

    def recalculate(self):
        self.result = self.param1 * self.param2


class TableModel(QtCore.QAbstractTableModel):
    def __init__(self, data_list, h_headers, v_headers, parent=None):
        super().__init__(parent)
        self.data_list = data_list
        self.h_headers = h_headers
        self.v_headers = v_headers

    def rowCount(self, parent):
        return len(self.v_headers)

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

    def data(self, index, role):
        row = index.row()
        column = index.column()
        item = self.data_list[row][column]
        if role in (QtCore.Qt.DisplayRole, ResultRole):
            return item.result
        elif role == Param1Role:
            return item.param1
        elif role == Param2Role:
            return item.param2

    def setData(self, index, value, role=QtCore.Qt.EditRole):
        row = index.row()
        column = index.column()
        item = self.data_list[row][column]
        if role == Param1Role:
            item.param1 = value
            item.recalculate()
            self.dataChanged.emit(
                index, index, (ResultRole, QtCore.Qt.DisplayRole, role)
            )
            return True
        elif role == Param2Role:
            item.param2 = value
            item.recalculate()
            self.dataChanged.emit(
                index, index, (ResultRole, QtCore.Qt.DisplayRole, role)
            )
            return True
        return False

    def headerData(self, section, orientation, role):
        if role == QtCore.Qt.DisplayRole:
            if orientation == QtCore.Qt.Horizontal:
                return self.h_headers[section]
            if orientation == QtCore.Qt.Vertical:
                return self.v_headers[section]

    def flags(self, index):
        return (
            QtCore.Qt.ItemIsEditable
            | QtCore.Qt.ItemIsEnabled
            | QtCore.Qt.ItemIsSelectable
            | QtCore.Qt.ItemIsDragEnabled
            | QtCore.Qt.ItemIsDropEnabled
        )


class ReadOnlyDelegate(QtWidgets.QStyledItemDelegate):
    def createEditor(self, parent, option, index):
        pass


class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        data = []
        for i in range(3):
            row_items = []
            for i in range(3):
                item = Item(*random.sample(range(100), 2))
                row_items.append(item)
            data.append(row_items)
        h_headers = ["h1", "h2", "h3"]
        v_headers = ["v1", "v2", "v3"]

        self.view = QtWidgets.QTableView()
        delegate = ReadOnlyDelegate(self.view)
        self.view.setItemDelegate(delegate)
        model = TableModel(data, h_headers, v_headers)
        self.view.setModel(model)

        self.param1_spinbox = QtWidgets.QDoubleSpinBox()
        self.param2_spinbox = QtWidgets.QDoubleSpinBox()
        self.result_label = QtWidgets.QLabel()

        self.view.selectionModel().currentChanged.connect(self.update_from_model)
        self.view.model().dataChanged.connect(self.update_from_model)

        self.param1_spinbox.valueChanged.connect(self.update_to_model)
        self.param2_spinbox.valueChanged.connect(self.update_to_model)

        lay = QtWidgets.QFormLayout(self)
        lay.addRow(self.view)
        lay.addRow("Param 1", self.param1_spinbox)
        lay.addRow("Param 2", self.param2_spinbox)
        lay.addRow("Result", self.result_label)
        self.resize(640, 480)

    def update_from_model(self):
        index = self.view.selectionModel().currentIndex()
        param1 = index.data(Param1Role)
        param2 = index.data(Param2Role)
        result = index.data(ResultRole)

        self.param1_spinbox.blockSignals(True)
        self.param1_spinbox.setValue(param1)
        self.param1_spinbox.blockSignals(False)

        self.param2_spinbox.blockSignals(True)
        self.param2_spinbox.setValue(param2)
        self.param2_spinbox.blockSignals(False)

        self.result_label.setNum(result)

    def update_to_model(self):
        index = self.view.selectionModel().currentIndex()
        param1 = self.param1_spinbox.value()
        param2 = self.param2_spinbox.value()
        self.view.model().setData(index, param1, Param1Role)
        self.view.model().setData(index, param2, Param2Role)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec_())