我为 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()
问题是您创建的 QModelIndexes 与 不一致
createIndex()
。在
index()
覆盖中,您使用节点本身作为 的指针createIndex()
:在 中
get_index()
,您使用父级:parent_index = self.get_index(parent_node)
这会导致不一致的行为,您可以通过打印块内创建的数据来验证这一点if notify
:这将输出:
它“起作用”(意味着子项已正确创建)的原因是子项实际上已添加到正确的父节点。您没有立即看到它的原因是因为您在 中提供了错误的父级 QModelIndex()
beginInsertRow()
,因此视图不知道如何处理它,因为给定的父级不是应该进一步显示的父级子项目,因此它只是忽略它。解决方案很简单:更改 的最后一个参数以
createIndex()
反映相同的行为:请注意,您的
parent()
实现也是不一致的,因为您使用了错误的指针和引用。我们来分析一下它的代码:parent()
应该返回给定项目的 QModelIndex,但实际上表示该项目相对于父项row_within_parent
的行。那么不一致,因为上面的行不代表用于它的正确指针。相反,您应该通过祖父母找到父母的行。createIndex()
这也反映在分支线的不一致显示上(在某些 OS/QStyles 上可能不会显示):
正如您所看到的,“cat”项目不显示父级的分支线(与“dog”不同),而顶级“birds”项目显示不应该存在的分支,因为没有其他分支其后的兄弟姐妹。
这是该函数的固定版本
parent()
:并正确绘制树枝:
最后,您这样做可能是为了测试目的,但您应该非常小心在 中执行的操作
showEvent()
,因为它可以通过多种方式触发,并且完全不受用户控制。如果您想在第一次显示小部件时执行某些操作,请使用
QTimer.singleShot(0, someFunction)
或使用内部布尔标志。实际上,上述内容可能是不必要的,因为视图/模型通常不受延迟显示事件的影响,并且它们有内部方法来处理因稍后显示而延迟的更新和几何要求。