为什么 QTreeView 不将新添加的节点显示到 QAbstractItemModel 中的非根节点

问题描述 投票:0回答:1

我为 QTreeView 创建了一个自定义模型。显示问题的完整最小代码如下。如果我向根节点添加一个新节点,通过单击“添加级别 1”,它就会显示。但是,如果我通过单击“添加级别 2”将新节点添加到第二级别,则它不会显示。仅当我折叠父节点然后再次展开它时,该节点才会显示。我的

MyTreeModel
哪一部分出了问题?

我添加了 QT 标签,即使我的代码是 PySide6,因为错误可能在于我对 QAbstractItemModel 方法的理解,这不是 Python 或 PySide 特有的东西。

完整代码

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()
qt pyside pyside6 qt6
1个回答
0
投票

问题是您创建的 QModelIndexes 与

createIndex()
不一致。

index()
覆盖中,您使用节点本身作为
createIndex()
的指针:

    def index(self, row, column, parentIndex):
        ...
        if child_node:
            return self.createIndex(row, column, child_node)

get_index()
中,您可以使用父级:

    def get_index(self, node: TreeNode):
        ...
        return self.createIndex(row, 0, node.parent_node)

这会导致行为不一致,您可以通过打印在

parent_index = self.get_index(parent_node)
块中创建的
if notify
的数据来验证这一点:

        if notify:
            parent_index = self.get_index(parent_node)
            print('parent:', parent_index.data()) # < add this line

将输出:

parent: root

它“起作用”的原因(意味着子项已正确创建)是子项“实际上”添加到了正确的父项节点。您没有立即看到它的原因是因为您在 beginInsertRow() 中提供了错误父 QModelIndex()。

解决方案很简单:更改 

createIndex()

的最后一个参数以反映相同的行为:

    def get_index(self, node: TreeNode):
        ...
        return self.createIndex(row, 0, node)

请注意,您的 
parent()

实现也不一致,因为您使用了错误的指针和引用。我们来分析一下它的代码:

    def parent(self, index):
        # correct
        if not index.isValid():
            return QModelIndex()

        childNode = index.internalPointer()
        parentNode = childNode.parent_node

        # again, correct
        if parentNode == self.root_node:
            return QModelIndex()

        # this only finds the row within the parent!
        row_within_parent = parentNode.children.index(childNode)

        # this returns an inconsistent index!
        return self.createIndex(row_within_parent, 0, parentNode)

parent()

应返回给定项目的 QModelIndex,但

row_within_parent
实际上表示项目
相对于父项
的行。 createIndex() 则不一致,因为上面的行并不代表其使用的正确指针。相反,您应该通过
祖父母
找到父级的行。 这也反映在分支线显示不一致(在某些操作系统/QStyles 上可能不显示):

如您所见,“cat”项不显示父级的分支线(与“dog”不同),而顶级“birds”项显示不应该存在的分支,因为没有之后还有其他兄弟姐妹。

这是

parent()

函数的固定版本:

    def parent(self, index):
        if not index.isValid():
            return QModelIndex()

        childNode = index.internalPointer()
        parentNode = childNode.parent_node

        if parentNode == self.root_node:
            return QModelIndex()

        grandParent = parentNode.parent_node

        row_within_parent = grandParent.children.index(parentNode)

        return self.createIndex(row_within_parent, 0, parentNode)

正确绘制树枝:

最后,您这样做可能是为了测试目的,但您应该非常小心在

showEvent()

中执行的操作,因为它可以通过多种方式触发,并且完全不受用户控制。

如果您想在

第一次

显示小部件时执行某些操作,则可以使用QTimer.singleShot(0, someFunction),或使用内部布尔标志。

class MainWindow(QMainWindow):
    def __init__(self):
        ...
        QTimer.singleShot(0, self.initModel)

    def initModel(self):
        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()

# alternatively:

class MainWindow(QMainWindow):
    _firstShow = False
    def showEvent(self, event):
        if not self._firstShow:
            self._firstShow = True
            my_model = MyTreeModel()
            ...

实际上,上述内容可能是不必要的,因为视图/模型通常不受延迟显示事件的影响,并且它们有内部方法来处理因稍后显示而延迟的更新和几何要求。

© www.soinside.com 2019 - 2024. All rights reserved.