我为 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()
的指针:
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()
...
实际上,上述内容可能是不必要的,因为视图/模型通常不受延迟显示事件的影响,并且它们有内部方法来处理因稍后显示而延迟的更新和几何要求。