QAbstractItemModel:创建具有映射子项的父元素表

问题描述

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

enter image description here

我以前曾经使用过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,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,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())

解决方法

模型不必是树型的,因为具有角色的类型表的模型就足够了。也不应使用QDataWidgetMapper,因为要使用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,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,(ResultRole,QtCore.Qt.DisplayRole,role)
            )
            return True
        elif role == Param2Role:
            item.param2 = value
            item.recalculate()
            self.dataChanged.emit(
                index,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,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_())