You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt:基于模型/视图框架存储多维数据并在QTableView展示

实现方案:单数据源多视图展示

要实现单个三维数据源在不同表格中以不同维度展示,核心思路是用一个数据存储类封装原始数据,为每个表格创建独立的适配模型,共享同一个数据存储实例。数据变更时,所有关联模型会自动通知视图更新,完全契合PyQt模型/视图架构的设计理念。

1. 封装核心数据存储

先实现一个DataStore类,负责存储原始三维数据、提供数据访问接口,并在数据变化时触发信号通知关联模型:

from PyQt5.QtCore import QObject, pyqtSignal

class DataStore(QObject):
    dataChanged = pyqtSignal()  # 数据变更触发的信号

    def __init__(self, initial_data=None):
        super().__init__()
        self._data = initial_data or {}

    def get_record(self, record_name):
        return self._data.get(record_name, {})

    def get_attribute(self, record_name, attr_name):
        return self.get_record(record_name).get(attr_name, {})

    def get_value(self, record_name, attr_name, source):
        return self.get_attribute(record_name, attr_name).get(source, "")

    def set_value(self, record_name, attr_name, source, value):
        if record_name not in self._data:
            self._data[record_name] = {}
        if attr_name not in self._data[record_name]:
            self._data[record_name][attr_name] = {}
        self._data[record_name][attr_name][source] = value
        self.dataChanged.emit()  # 通知模型更新

    @property
    def records(self):
        return list(self._data.keys())

    @property
    def attributes(self):
        if not self._data:
            return []
        return list(next(iter(self._data.values())).keys())

    @property
    def sources(self):
        if not self._data or not next(iter(self._data.values())):
            return []
        return list(next(iter(next(iter(self._data.values())).values())).keys())

2. 为Table1创建适配模型

Table1需要展示:行对应记录(record)、列对应属性(attribute),值为edited数据源的数据。继承QAbstractTableModel实现该视图的模型逻辑:

from PyQt5.QtCore import QAbstractTableModel, Qt, QModelIndex

class RecordAttributeModel(QAbstractTableModel):
    def __init__(self, data_store, parent=None):
        super().__init__(parent)
        self.data_store = data_store
        self.data_store.dataChanged.connect(self.onDataChanged)

    def rowCount(self, parent=QModelIndex()):
        return len(self.data_store.records)

    def columnCount(self, parent=QModelIndex()):
        return len(self.data_store.attributes)

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid() or role != Qt.DisplayRole:
            return None
        record_name = self.data_store.records[index.row()]
        attr_name = self.data_store.attributes[index.column()]
        return self.data_store.get_value(record_name, attr_name, "edited")

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if role != Qt.DisplayRole:
            return None
        if orientation == Qt.Horizontal:
            return self.data_store.attributes[section]
        else:
            return f"{section+1}:"  # 显示行号格式如1:、2:

    def onDataChanged(self):
        self.beginResetModel()
        self.endResetModel()

3. 为Table2创建适配模型

Table2需要展示单个记录的属性与多数据源对比:行对应属性(attribute)、列对应数据源(data_source),支持切换展示的记录:

class AttributeSourceModel(QAbstractTableModel):
    def __init__(self, data_store, parent=None):
        super().__init__(parent)
        self.data_store = data_store
        self.current_record = None
        self.data_store.dataChanged.connect(self.onDataChanged)

    def set_current_record(self, record_name):
        self.beginResetModel()
        self.current_record = record_name
        self.endResetModel()

    def rowCount(self, parent=QModelIndex()):
        if not self.current_record:
            return 0
        return len(self.data_store.attributes)

    def columnCount(self, parent=QModelIndex()):
        return len(self.data_store.sources)

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid() or not self.current_record or role != Qt.DisplayRole:
            return None
        attr_name = self.data_store.attributes[index.row()]
        source = self.data_store.sources[index.column()]
        return self.data_store.get_value(self.current_record, attr_name, source)

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if role != Qt.DisplayRole:
            return None
        if orientation == Qt.Horizontal:
            return self.data_store.sources[section]
        else:
            return self.data_store.attributes[section]

    def onDataChanged(self):
        self.beginResetModel()
        self.endResetModel()

4. 主窗口整合与联动

将上述组件整合到主窗口,实现Table1点击行时,Table2自动切换到对应记录的展示:

from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QHBoxLayout, QTableView

class MainWindow(QMainWindow):
    def __init__(self, data_store):
        super().__init__()
        self.setWindowTitle("多视图数据展示")
        self.data_store = data_store

        # 初始化模型与视图
        self.model1 = RecordAttributeModel(data_store)
        self.model2 = AttributeSourceModel(data_store)

        self.table1 = QTableView()
        self.table1.setModel(self.model1)
        self.table1.clicked.connect(self.onTable1Clicked)

        self.table2 = QTableView()
        self.table2.setModel(self.model2)
        # 默认显示第一条记录
        if self.data_store.records:
            self.model2.set_current_record(self.data_store.records[0])
            self.table2.setWindowTitle(f"Record: {self.data_store.records[0]}")

        # 布局设置
        central_widget = QWidget()
        layout = QHBoxLayout(central_widget)
        layout.addWidget(self.table1)
        layout.addWidget(self.table2)
        self.setCentralWidget(central_widget)

    def onTable1Clicked(self, index):
        record_name = self.data_store.records[index.row()]
        self.model2.set_current_record(record_name)
        self.table2.setWindowTitle(f"Record: {record_name}")

if __name__ == "__main__":
    import sys
    # 测试数据初始化
    test_data = {
        "record1": {
            "name": {"csv": "a", "edited": "b", "db": "c"},
            "address": {"csv": "d", "edited": "e", "db": "f"},
            "phone": {"csv": "g", "edited": "h", "db": "i"},
        },
        "record2": {
            "name": {"csv": "j", "edited": "k", "db": "l"},
            "address": {"csv": "m", "edited": "n", "db": "o"},
            "phone": {"csv": "p", "edited": "q", "db": "r"},
        },
        "record3": {
            "name": {"csv": "s", "edited": "t", "db": "u"},
            "address": {"csv": "v", "edited": "w", "db": "x"},
            "phone": {"csv": "y", "edited": "z", "db": "aa"},
        }
    }
    app = QApplication(sys.argv)
    store = DataStore(test_data)
    window = MainWindow(store)
    window.show()
    sys.exit(app.exec_())

核心优势

  • 单一数据源:所有视图共享同一数据存储,修改数据只需操作一次,自动同步到所有视图。
  • 职责分离:每个模型只负责对应视图的展示逻辑,代码结构清晰,易于扩展维护。
  • 灵活扩展:新增其他维度的视图时,只需新增对应的适配模型,无需修改核心数据存储。

内容的提问来源于stack exchange,提问作者Jesse Reilly

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 06:35:28