使用QSortFilterProxyModel后QTreeView插入数据不更新的问题
问题分析与解决
一、QAbstractItemModel与QSortFilterProxyModel的交互机制
QSortFilterProxyModel是数据代理中间层,本身不存储业务数据,完全依赖绑定的源模型(QAbstractItemModel)提供数据。它的核心交互逻辑是:
- 绑定源模型后,代理模型会监听源模型发出的所有数据变更信号(如
rowsInserted、dataChanged等)。 - 源模型修改数据时,必须主动发射对应信号,代理模型才能感知到变化,进而更新自身过滤结果,最终通知视图(QTreeView)刷新显示。
- 如果源模型修改数据后不发射信号,代理模型和视图无法感知数据变化,自然不会显示新内容。
二、除setSourceModel外的关键配置/实现要求
1. 源模型必须严格实现数据变更的信号发射
这是你当前问题的核心原因:你的Model类中newData()方法直接修改_root.children列表,但没有发射beginInsertRows和endInsertRows信号,导致代理模型完全不知道有新数据插入。
2. 代理模型的过滤规则配置(你已正确实现)
你已经设置了setFilterKeyColumn(0)和setRecursiveFilteringEnabled(True),确保了递归过滤第一列内容,这部分无需修改。
3. 源模型的index/parent方法正确实现(你已正确实现)
你的index()和parent()方法逻辑正确,能构建模型索引的父子关系,这是代理模型正确映射数据的基础。
三、修正后的代码
主要修改Model类的newData()方法,在插入子项前后发射数据变更信号:
import logging import sys from PySide6 import QtCore, QtWidgets from PySide6.QtCore import QSortFilterProxyModel class DBObject: def __init__(self, name, parent, children=None): self.name = name self.parent = parent self.children = children or list() def __repr__(self): return f"name: {self.name}, parent: {self.parent.name if self.parent is not None else '-'}" class Model(QtCore.QAbstractItemModel): def __init__(self, parent=None): super().__init__(parent) self._root = DBObject("root", None) def newData(self): items = ["foo", "bar", "baz"] # 获取根节点的模型索引 root_index = self.index(0, 0, QtCore.QModelIndex()) # 插入一级子项(x+0) start_row = len(self._root.children) end_row = start_row + len(items) - 1 self.beginInsertRows(root_index, start_row, end_row) for x in items: child = DBObject(x + "0", self._root) self._root.children.append(child) # 插入二级子项(y+1) child_index = self.index(len(self._root.children)-1, 0, root_index) child_start_row = len(child.children) child_end_row = child_start_row + len(items) - 1 self.beginInsertRows(child_index, child_start_row, child_end_row) for y in items: child.children.append(DBObject(y + "1", child)) self.endInsertRows() self.endInsertRows() def columnCount(self, parent=QtCore.QModelIndex()): return 1 def rowCount(self, parent=QtCore.QModelIndex()): if not parent.isValid(): return 1 parentItem = parent.internalPointer() rowCount = len(parentItem.children) logging.info(f"rowCount({parentItem}): rowCount={rowCount}") return rowCount def parent(self, index): if not index.isValid(): return QtCore.QModelIndex() item = index.internalPointer() parentItem = item.parent logging.info(f"parent({item}): parent={parentItem}") if parentItem is None: return QtCore.QModelIndex() else: if parentItem.parent is None: return self.createIndex(0, 0, parentItem) else: return self.createIndex(parentItem.parent.children.index(parentItem), 0, parentItem) def index(self, row, column, parent=QtCore.QModelIndex()): if not parent.isValid(): if row != 0 or column != 0: return QtCore.QModelIndex() else: logging.info(f"index({row}, {column}, None): index={self._root}") return self.createIndex(0, 0, self._root) parentItem = parent.internalPointer() if 0 <= row < len(parentItem.children): logging.info(f"index({row}, {column}, {parentItem}): index={parentItem.children[row]}") return self.createIndex(row, column, parentItem.children[row]) else: logging.info(f"index({row}, {column}, {parentItem}): index=None") return QtCore.QModelIndex() def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole): if not index.isValid(): return None item = index.internalPointer() if role == QtCore.Qt.ItemDataRole.DisplayRole: return item.name else: return None def flags(self, index): if not index.isValid(): return QtCore.Qt.ItemFlag.NoItemFlags return ( QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsSelectable) class ProxyModel(QSortFilterProxyModel): def __init__(self, parent=None): super().__init__(parent) self.setFilterKeyColumn(0) self.setRecursiveFilteringEnabled(True) class MainWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.setMinimumSize(640, 480) centralWidget = QtWidgets.QWidget(self) self.setCentralWidget(centralWidget) layout = QtWidgets.QVBoxLayout(centralWidget) self._treeView = QtWidgets.QTreeView(self) layout.addWidget(self._treeView) self._model = Model() self._proxyModel = ProxyModel() self._proxyModel.setSourceModel(self._model) self._treeView.setModel(self._proxyModel) self._proxyModel.setFilterFixedString("bar1") button = QtWidgets.QPushButton("Add") layout.addWidget(button) button.clicked.connect(self._Clicked) def _Clicked(self): self._model.newData() self._treeView.expandAll() def main(): app = QtWidgets.QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() if __name__ == "__main__": main()
四、关键修改说明
在newData()中,插入每一层子项时都遵循以下流程:
- 调用
beginInsertRows(parent_index, start_row, end_row):通知模型和代理,从start_row到end_row的行将被插入到parent_index对应的父项之下。 - 执行实际的数据插入操作(修改
children列表)。 - 调用
endInsertRows():通知插入完成,触发模型更新信号。
这样代理模型就能接收到rowsInserted信号,更新过滤后的数据集,视图也会自动刷新显示新内容。
内容的提问来源于stack exchange,提问作者lebhero
相关产品推荐
相关产品推荐

