AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / coding / 问题 / 78372761
Accepted
Damn Vegetables
Damn Vegetables
Asked: 2024-04-23 21:30:09 +0800 CST2024-04-23 21:30:09 +0800 CST 2024-04-23 21:30:09 +0800 CST

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

  • 772

我为 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
  • 1 1 个回答
  • 29 Views

1 个回答

  • Voted
  1. Best Answer
    musicamante
    2024-04-24T04:19:25+08:002024-04-24T04:19:25+08:00

    问题是您创建的 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
    

    它“起作用”(意味着子项已正确创建)的原因是子项实际上已添加到正确的父节点。您没有立即看到它的原因是因为您在 中提供了错误的父级 QModelIndex() beginInsertRow(),因此视图不知道如何处理它,因为给定的父级不是应该进一步显示的父级子项目,因此它只是忽略它。

    解决方案很简单:更改 的最后一个参数以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()

    这也反映在分支线的不一致显示上(在某些 OS/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()
                ...
    

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

    • 1

相关问题

  • 当按钮悬停在 Qt6 中时更改 QML 按钮的文本颜色?

  • 什么会导致 OpcUa 中发生 BadTimeout?

  • 为什么我将QListwidget的ItemClicked信号连接到槽函数后,QListWidgetItem的复选框无法正常点击?

  • 将项目拖出 MouseArea 时如何更改光标形状?

  • 在 Qt 中重用窗口[重复]

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    Vue 3:创建时出错“预期标识符但发现‘导入’”[重复]

    • 1 个回答
  • Marko Smith

    为什么这个简单而小的 Java 代码在所有 Graal JVM 上的运行速度都快 30 倍,但在任何 Oracle JVM 上却不行?

    • 1 个回答
  • Marko Smith

    具有指定基础类型但没有枚举器的“枚举类”的用途是什么?

    • 1 个回答
  • Marko Smith

    如何修复未手动导入的模块的 MODULE_NOT_FOUND 错误?

    • 6 个回答
  • Marko Smith

    `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它?

    • 3 个回答
  • Marko Smith

    何时应使用 std::inplace_vector 而不是 std::vector?

    • 3 个回答
  • Marko Smith

    在 C++ 中,一个不执行任何操作的空程序需要 204KB 的堆,但在 C 中则不需要

    • 1 个回答
  • Marko Smith

    PowerBI 目前与 BigQuery 不兼容:Simba 驱动程序与 Windows 更新有关

    • 2 个回答
  • Marko Smith

    AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String”

    • 1 个回答
  • Marko Smith

    我正在尝试仅使用海龟随机和数学模块来制作吃豆人游戏

    • 1 个回答
  • Martin Hope
    Aleksandr Dubinsky 为什么 InetAddress 上的 switch 模式匹配会失败,并出现“未涵盖所有可能的输入值”? 2024-12-23 06:56:21 +0800 CST
  • Martin Hope
    Phillip Borge 为什么这个简单而小的 Java 代码在所有 Graal JVM 上的运行速度都快 30 倍,但在任何 Oracle JVM 上却不行? 2024-12-12 20:46:46 +0800 CST
  • Martin Hope
    Oodini 具有指定基础类型但没有枚举器的“枚举类”的用途是什么? 2024-12-12 06:27:11 +0800 CST
  • Martin Hope
    sleeptightAnsiC `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它? 2024-11-09 07:18:53 +0800 CST
  • Martin Hope
    The Mad Gamer 何时应使用 std::inplace_vector 而不是 std::vector? 2024-10-29 23:01:00 +0800 CST
  • Martin Hope
    Chad Feller 在 5.2 版中,bash 条件语句中的 [[ .. ]] 中的分号现在是可选的吗? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench 为什么双破折号 (--) 会导致此 MariaDB 子句评估为 true? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng 为什么 `dict(id=1, **{'id': 2})` 有时会引发 `KeyError: 'id'` 而不是 TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String” 2024-03-20 03:12:31 +0800 CST
  • Martin Hope
    MarkB 为什么 GCC 生成有条件执行 SIMD 实现的代码? 2024-02-17 06:17:14 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve