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
相关产品推荐
相关产品推荐

