QTreeView无法显示QAbstractItemModel中非根节点新增子节点问题排查
自定义QAbstractItemModel添加二级节点不即时显示的问题分析及修复
问题描述
为QTreeView实现自定义MyTreeModel后,点击“Add Level 1”按钮给根节点新增子节点时显示正常;但点击“Add Level 2”按钮给二级节点(如mammals)新增子节点时,新节点不会立即显示,必须折叠并重新展开父节点才会出现。
复现代码
from __future__ import annotations from typing import Optional from PySide6.QtCore import QAbstractItemModel, QModelIndex from PySide6.QtGui import Qt from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, QHBoxLayout, QTreeView class TreeNode: def __init__(self, name: str, parent_node): self.name = name self.parent_node = parent_node self.children: list[TreeNode] = [] def get_child_by_name(self, name) -> Optional[TreeNode]: for child in self.children: if child.name == name: return child return None class MyTreeModel(QAbstractItemModel): def __init__(self): super().__init__() self.root_node = TreeNode("root", None) def headerData(self, section, orientation, role=Qt.DisplayRole): if orientation == Qt.Horizontal and role == Qt.DisplayRole: return "Name" def rowCount(self, parentIndex): if not parentIndex.isValid(): parentNode = self.root_node else: parentNode = parentIndex.internalPointer() return len(parentNode.children) def columnCount(self, parent): return 1 def data(self, index, role): if not index.isValid(): return None if role == Qt.DisplayRole: node: TreeNode = index.internalPointer() column = index.column() match column: case 0: return node.name case _: return None else: return None def parent(self, index): if not index.isValid(): return QModelIndex() childNode: TreeNode = index.internalPointer() parentNode = childNode.parent_node if parentNode == self.root_node: return QModelIndex() row_within_parent = parentNode.children.index(childNode) return self.createIndex(row_within_parent, 0, parentNode); def index(self, row, column, parentIndex): if not self.hasIndex(row, column, parentIndex): return QModelIndex() if not parentIndex.isValid(): parentNode = self.root_node else: parentNode = parentIndex.internalPointer() child_node = parentNode.children[row] if child_node: return self.createIndex(row, column, child_node) else: return QModelIndex() def set_data(self, data: []): self.beginResetModel() self.apply_data(data) self.endResetModel() def update_data(self, data: []): self.apply_data(data, True) def apply_data(self, data, notify=False): for item in data: parent_node = self.root_node; for part in item.split("/"): existing = parent_node.get_child_by_name(part) if existing: parent_node = existing else: if notify: parent_index = self.get_index(parent_node) count = len(parent_node.children) self.beginInsertRows(parent_index, count, count) new_node = TreeNode(part, parent_node) parent_node.children.append(new_node) parent_node = new_node if notify: self.endInsertRows() def get_index(self, node: TreeNode): if not node.parent_node: return QModelIndex() row = node.parent_node.children.index(node) return self.createIndex(row, 0, node.parent_node) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(600, 400) button1 = QPushButton("Add Level 1") button1.clicked.connect(self.add1) button2 = QPushButton("Add Level 2") button2.clicked.connect(self.add2) row1 = QHBoxLayout() row1.setAlignment(Qt.AlignLeft) row1.addWidget(button1) row1.addWidget(button2) self.tree_view = QTreeView() layout = QVBoxLayout() layout.addLayout(row1) layout.addWidget(self.tree_view, stretch=1) self.central_widget = QWidget() self.central_widget.setLayout(layout) self.setCentralWidget(self.central_widget) def showEvent(self, event): data = ["mammals", "birds", "mammals/dog", "birds/eagle", "mammals/cat"] my_model = MyTreeModel() my_model.set_data(data) self.tree_view.setModel(my_model) self.tree_view.expandAll() def add1(self): data = ["reptiles"] m = self.tree_view.model() m.update_data(data) def add2(self): data = ["mammals/rat"] m = self.tree_view.model() m.update_data(data) app = QApplication([]) win = MainWindow() win.show() app.exec()
问题根源
核心错误出在get_index方法的实现:该方法本应返回传入节点对应的QModelIndex,但当前代码生成的索引指向的是传入节点的父节点,而非节点本身。
当调用beginInsertRows(parent_index, ...)时,传入的parent_index必须是要添加子节点的父节点对应的索引。错误的父索引会导致视图无法识别需要更新的节点区域,因此新增的子节点不会即时刷新,只有折叠展开父节点触发视图重新读取模型数据时才会显示。
原错误代码:
def get_index(self, node: TreeNode): if not node.parent_node: return QModelIndex() row = node.parent_node.children.index(node) return self.createIndex(row, 0, node.parent_node)
这里createIndex的第三个参数(internalPointer)传入的是node.parent_node,导致生成的索引指向父节点,而非当前node。
修复方案
修改get_index方法,使其返回正确的节点索引:
def get_index(self, node: TreeNode): if node == self.root_node: return QModelIndex() # 获取当前节点在父节点中的行号 row_in_parent = node.parent_node.children.index(node) # 创建并返回当前节点对应的索引,internalPointer传入当前node return self.createIndex(row_in_parent, 0, node)
修改后,beginInsertRows将收到正确的父节点索引,视图会立即更新该父节点的子节点列表,新增的二级节点会即时显示。
验证效果
修改代码后,点击“Add Level 2”按钮,mammals节点下会立即出现新增的rat子节点,无需折叠展开父节点。
内容的提问来源于stack exchange,提问作者Damn Vegetables
相关产品推荐
相关产品推荐

