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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 14:05:16