QTreeView QAbstractItemModel 父级在删除项后崩溃,有时会崩溃 [英] QTreeView QAbstractItemModel parent collapses after deleting item and sometimes crashes
问题描述
我正在尝试在 QTreeView 中构建一个小条目列表,并基于
我的理解是,这是因为删除项目的索引会发生变化,并且为了防止可以根据此线程使用 QtCore.QPersistentModelIndex()
:如何删除QTableView小部件中的多行?
尽管该示例使用了 QStandardItemModel()
,但由于我的示例使用了 QAbstractItemModel()
,我该如何实现类似的概念,并防止它崩溃...?
导入系统从 functools 导入部分从 PyQt4 导入 QtGui、QtCoreHORIZONTAL_HEADERS =(资产名称",添加日期")类资产类(对象):'''一个简单的自定义数据对象'''def __init__(self, **kwargs):如果不是 kwargs.get('name') 或不是 kwargs.get('type'):返回self.name = kwargs.get('name')self.date_Added = kwargs.get('date_ added')self.type = kwargs.get('type')def __repr__(self):返回%s - %s %s"% (self.type, self.name, self.date_ added)类树项目(对象):'''用于返回行/列数据的python对象,并注意这是父母和/或孩子'''def __init__(self, asset, header, parent_item):self.asset = 资产self.parent_item = parent_itemself.header = 标题self.child_items = []def appendChild(self, item):self.child_items.append(item)def removeChild(self, item):打印 'removeChild: item is %s' % item打印 'removeChild: self.child_items 是 %s' % self.child_itemsself.child_items.remove(item)定义孩子(自我,行):返回 self.child_items[row]def childCount(self):返回 len(self.child_items)def columnCount(self):返回 2定义数据(自我,列):如果 self.asset == 无:如果列 == 0:返回 QtCore.QVariant(self.header)如果列 == 1:返回 QtCore.QVariant("")别的:如果列 == 0:返回 QtCore.QVariant(self.asset.name)如果列 == 1:返回 QtCore.QVariant(self.asset.date_ added)返回 QtCore.QVariant()定义父(自己):返回 self.parent_item定义行(自我):如果 self.parent_item:返回 self.parent_item.child_items.index(self)返回 0类 TreeModel(QtCore.QAbstractItemModel):'''显示几个名字的模型,按性别排序'''def __init__(self, parent=None):super(TreeModel, self).__init__(parent)self.assets = []模型数据 = ((车辆",卡车",2020 年 5 月 27 日"),(车辆"、汽车"、2020 年 5 月 25 日"),(CHARACTER",Peter",2020 年 5 月 27 日"),(CHARACTER",Rachel",2020 年 5 月 29 日"),(PROP",主席",2020 年 5 月 27 日"),(PROP"、Axe"、2020 年 5 月 17 日"))对于模型数据中的资产类型、名称、日期:资产 = 资产类(类型=资产类型,名称=名称,添加日期=日期)self.assets.append(资产)self.rootItem = TreeItem(无,所有",无)self.parents = {0: self.rootItem}self.setupModelData()def columnCount(self, parent=None):如果 parent 和 parent.isValid():返回 parent.internalPointer().columnCount()别的:返回 len(HORIZONTAL_HEADERS)定义数据(自我,索引,角色):如果不是 index.isValid():返回 QtCore.QVariant()item = index.internalPointer()如果角色== QtCore.Qt.DisplayRole:返回 item.data(index.column())如果角色 == QtCore.Qt.UserRole:如果项目:返回 item.asset返回 QtCore.QVariant()def headerData(自我,列,方向,角色):如果(方向 == QtCore.Qt.Horizontal 和角色 == QtCore.Qt.DisplayRole):尝试:返回 QtCore.QVariant(HORIZONTAL_HEADERS[column])除了索引错误:经过返回 QtCore.QVariant()定义索引(自我,行,列,父):如果不是 self.hasIndex(row, column, parent):返回 QtCore.QModelIndex()如果不是 parent.isValid():parent_item = self.rootItem别的:parent_item = parent.internalPointer()childItem = parent_item.child(row)如果子项:返回 self.createIndex(row, column, childItem)别的:返回 QtCore.QModelIndex()def parent(self, index):如果不是 index.isValid():返回 QtCore.QModelIndex()childItem = index.internalPointer()如果不是 childItem:返回 QtCore.QModelIndex()parent_item = childItem.parent()如果 parent_item == self.rootItem:返回 QtCore.QModelIndex()返回 self.createIndex(parent_item.row(), 0, parent_item)def rowCount(self, parent=QtCore.QModelIndex()):如果 parent.column() >0:返回 0如果不是 parent.isValid():p_Item = self.rootItem别的:p_Item = parent.internalPointer()返回 p_Item.childCount()def setupModelData(self):对于 self.assets 中的资产:资产类型 = 资产类型如果不是 self.parents.has_key(asset_type):new_parent = TreeItem(无,资产类型,self.rootItem)self.rootItem.appendChild(new_parent)self.parents[asset_type] = new_parent打印 'self.parents:', self.parentsparent_item = self.parents[资产类型]new_item = TreeItem(资产,",parent_item)parent_item.appendChild(new_item)def addSubRow(self, new_asset):资产类型、名称、日期 = 新资产资产 = 资产类(类型=资产类型,名称=名称,添加日期=日期)parent_item = self.parents[资产类型]已经存在 = 假对于 parent_item.child_items 中的孩子:如果 child.asset.name == name 和 child.asset.type == asset_type:已经存在 = 真如果已经存在:打印此资产已存在"返回new_item = TreeItem(资产,",parent_item)parent_item.appendChild(new_item)def removeRow(self, rowIndexes):child_tree_item = rowIndexes[0].internalPointer()asset_type = rowIndexes[0].parent().data().toString()parent_item = self.parents[str(asset_type)]# 删除后保持树打开的提示:https://stackoverflow.com/questions/48121393/how-to-delete-multiple-rows-in-a-qtableview-widgetself.beginRemoveRows(QtCore.QModelIndex(), rowIndexes[0].row(), rowIndexes[0].row() + 1)parent_item.removeChild(child_tree_item)self.endRemoveRows()定义搜索模型(自我,资产):'''获取给定约会的模型索引'''定义搜索节点(节点):'''递归调用的函数,查看节点下的所有节点'''对于 node.child_items 中的孩子:打印 child.childCount()如果资产== child.asset:index = self.createIndex(child.row(), 0, child)回报指数如果 child.childCount() >0:结果 = searchNode(child)如果结果:返回结果retarg = searchNode(self.parents[0])打印重新输入返回重新输入def findAssetName(self, name):应用程序 = 无对于 self.assets 中的资产:打印资产名称如果资产名称 == 名称:应用 = 资产休息如果应用程序 != 无:索引 = self.searchModel(app)返回(真,索引)返回(假,无)类树视图(QtGui.QTreeView):right_button_clicked = QtCore.pyqtSignal(list, int)def __init__(self, parent=None):super(TreeView, self).__init__(parent)# self.clicked.connect(self.on_treeView_clicked)self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)self.customContextMenuRequested.connect(self.openMenu)def selectedRows(self):行 = []对于 self.selectedIndexes() 中的索引:如果 index.column() == 0:行.追加(索引.行())打印类型(行)返回行def openMenu(self, position):索引 = self.selectedIndexes()如果 len(indexes) >0:等级 = 0索引 = 索引[0]而 index.parent().isValid():index = index.parent()等级 += 1菜单 = QtGui.QMenu()编辑菜单 = 无如果级别 == 0:editMenu = QtGui.QAction(编辑人物", self)menu.addAction(editMenu)elif 级别 == 1:editMenu = QtGui.QAction(删除", self)menu.addAction(editMenu)elif 级别 == 2:editMenu = QtGui.QAction(编辑对象", self)menu.addAction(editMenu)如果编辑菜单:editMenu.triggered.connect(部分(self._on_right_click,索引,级别))menu.exec_(self.viewport().mapToGlobal(position))def _on_right_click(自我,索引,级别):self.right_button_clicked.emit(索引,级别)def delete_test(self):打印点击添加按钮"new_asset = ("CHARACTER", "Smith", 'May 28th, 2020')资产类型、名称、日期 = 新资产资产 = 资产类(类型=资产类型,名称=名称,添加日期=日期)parent_item = self.tree_view.model().parents[asset_type]已经存在 = 假对于 parent_item.child_items 中的孩子:如果 child.asset.name == name 和 child.asset.type == asset_type:已经存在 = 真如果已经存在:打印此资产已存在"返回new_item = TreeItem(资产,",parent_item)parent_item.appendChild(new_item)self.tree_view.model().layoutChanged.emit()类对话框(QtGui.QDialog):add_signal = QtCore.pyqtSignal(int)def __init__(self, parent=None):super(Dialog, self).__init__(parent)self.setMinimumSize(300, 150)self.model = TreeModel()布局 = QtGui.QVBoxLayout(self)self.tree_view = TreeView(self)self.tree_view.setModel(self.model)self.tree_view.setAlternatingRowColors(True)self.tree_view.right_button_clicked.connect(self.deleteButtonClicked)layout.addWidget(self.tree_view)label = QtGui.QLabel(搜索下面的人")layout.addWidget(标签)但是 = []frame = QtGui.QFrame(self)layout2 = QtGui.QHBoxLayout(frame)对于 self.model.assets 中的资产:但是 = QtGui.QPushButton(asset.name, frame)buts.append(but)layout2.addWidget(但是)QtCore.QObject.connect(but, QtCore.SIGNAL("clicked()"), self.but_clicked)layout.addWidget(frame)self.add_button = QtGui.QPushButton("添加\"角色-史密斯\"")layout.addWidget(self.add_button)QtCore.QObject.connect(self.add_button, QtCore.SIGNAL("clicked()"), self.addButtonClicked)self.delete_button = QtGui.QPushButton(删除选中")layout.addWidget(self.delete_button)QtCore.QObject.connect(self.delete_button, QtCore.SIGNAL("clicked()"), self.tree_view.clearSelection)self.but = QtGui.QPushButton(清除选择")layout.addWidget(self.but)QtCore.QObject.connect(self.but, QtCore.SIGNAL("clicked()"), self.tree_view.clearSelection)QtCore.QObject.connect(self.tree_view, QtCore.SIGNAL("clicked (QModelIndex)"), self.row_clicked)def row_clicked(self, index):'''单击一行时...显示名称'''打印 'row_clicked 索引类型:%s' % 索引打印 self.tree_view.model().data(index, QtCore.Qt.UserRole)def but_clicked(self):'''单击名称按钮时,我遍历模型,找到这个名字的人,并设置treeviews当前项目'''名称 = self.sender().text()打印BUTTON CLICKED:",名称结果,索引 = self.model.findAssetName(name)如果结果:如果索引:self.tree_view.setCurrentIndex(index)返回self.tree_view.clearSelection()def addButtonClicked(self):打印点击添加按钮"new_asset = ("CHARACTER", "Smith", 'May 28th, 2020')self.tree_view.model().addSubRow(new_asset)self.tree_view.model().layoutChanged.emit()@QtCore.pyqtSlot(list, int)def deleteButtonClicked(自我,索引,级别):打印 'deleteButton 点击'self.tree_view.model().removeRow(indexes)self.tree_view.model().layoutChanged.emit()如果 __name__ == __main__":app = QtGui.QApplication(sys.argv)对话 = 对话()对话框显示()sys.exit(app.exec_())
beginRemoveRows()
需要将 QModelIndex(要删除的 QModelIndex 的父级)作为第一个参数.关于您在表类型模型的代码注释中指出的示例,索引没有父级,因此传递了无效的 QModelIndex.
def removeRow(self, rowIndexes):child_tree_item = rowIndexes[0].internalPointer()parent_item = child_tree_item.parent()self.beginRemoveRows(rowIndexes[0].parent(), rowIndexes[0].row(), rowIndexes[0].row() + 1)parent_item.removeChild(child_tree_item)self.endRemoveRows()
Im trying to build a little list of entries within a QTreeView, and based on the example posted here, I got it to delete any child items via the right click context menu i added. but when i delete it the parent tree i have collapses. And in some cases if i delete a certain item in a certain order this crashes
My understanding is that this is because upon deletion the indices of the items changes, and to prevent that QtCore.QPersistentModelIndex()
can be utilized according to this thread: How to delete multiple rows in a QTableView widget?
Although that example uses a QStandardItemModel()
, since mine uses a QAbstractItemModel()
how can I implement a similar concept, and also prevent this from crashing...?
import sys
from functools import partial
from PyQt4 import QtGui, QtCore
HORIZONTAL_HEADERS = ("Asset Name", "Date Added")
class AssetClass(object):
'''
a trivial custom data object
'''
def __init__(self, **kwargs):
if not kwargs.get('name') or not kwargs.get('type'):
return
self.name = kwargs.get('name')
self.date_added = kwargs.get('date_added')
self.type = kwargs.get('type')
def __repr__(self):
return "%s - %s %s" % (self.type, self.name, self.date_added)
class TreeItem(object):
'''
a python object used to return row/column data, and keep note of
it's parents and/or children
'''
def __init__(self, asset, header, parent_item):
self.asset = asset
self.parent_item = parent_item
self.header = header
self.child_items = []
def appendChild(self, item):
self.child_items.append(item)
def removeChild(self, item):
print 'removeChild: item is %s' % item
print 'removeChild: self.child_items is %s' % self.child_items
self.child_items.remove(item)
def child(self, row):
return self.child_items[row]
def childCount(self):
return len(self.child_items)
def columnCount(self):
return 2
def data(self, column):
if self.asset == None:
if column == 0:
return QtCore.QVariant(self.header)
if column == 1:
return QtCore.QVariant("")
else:
if column == 0:
return QtCore.QVariant(self.asset.name)
if column == 1:
return QtCore.QVariant(self.asset.date_added)
return QtCore.QVariant()
def parent(self):
return self.parent_item
def row(self):
if self.parent_item:
return self.parent_item.child_items.index(self)
return 0
class TreeModel(QtCore.QAbstractItemModel):
'''
a model to display a few names, ordered by sex
'''
def __init__(self, parent=None):
super(TreeModel, self).__init__(parent)
self.assets = []
model_data = (("VEHICLE", "Truck", 'May 27th, 2020'),
("VEHICLE", "Car", 'May 25th, 2020'),
("CHARACTER", "Peter", 'May 27th, 2020'),
("CHARACTER", "Rachel", 'May 29th, 2020'),
("PROP", "Chair", 'May 27th, 2020'),
("PROP", "Axe", 'May 17th, 2020'))
for asset_type, name, date in model_data:
asset = AssetClass(type=asset_type, name=name, date_added=date)
self.assets.append(asset)
self.rootItem = TreeItem(None, "ALL", None)
self.parents = {0: self.rootItem}
self.setupModelData()
def columnCount(self, parent=None):
if parent and parent.isValid():
return parent.internalPointer().columnCount()
else:
return len(HORIZONTAL_HEADERS)
def data(self, index, role):
if not index.isValid():
return QtCore.QVariant()
item = index.internalPointer()
if role == QtCore.Qt.DisplayRole:
return item.data(index.column())
if role == QtCore.Qt.UserRole:
if item:
return item.asset
return QtCore.QVariant()
def headerData(self, column, orientation, role):
if (orientation == QtCore.Qt.Horizontal and
role == QtCore.Qt.DisplayRole):
try:
return QtCore.QVariant(HORIZONTAL_HEADERS[column])
except IndexError:
pass
return QtCore.QVariant()
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
if not parent.isValid():
parent_item = self.rootItem
else:
parent_item = parent.internalPointer()
childItem = parent_item.child(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
childItem = index.internalPointer()
if not childItem:
return QtCore.QModelIndex()
parent_item = childItem.parent()
if parent_item == self.rootItem:
return QtCore.QModelIndex()
return self.createIndex(parent_item.row(), 0, parent_item)
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.column() > 0:
return 0
if not parent.isValid():
p_Item = self.rootItem
else:
p_Item = parent.internalPointer()
return p_Item.childCount()
def setupModelData(self):
for asset in self.assets:
asset_type = asset.type
if not self.parents.has_key(asset_type):
new_parent = TreeItem(None, asset_type, self.rootItem)
self.rootItem.appendChild(new_parent)
self.parents[asset_type] = new_parent
print 'self.parents: ', self.parents
parent_item = self.parents[asset_type]
new_item = TreeItem(asset, "", parent_item)
parent_item.appendChild(new_item)
def addSubRow(self, new_asset):
asset_type, name, date = new_asset
asset = AssetClass(type=asset_type, name=name, date_added=date)
parent_item = self.parents[asset_type]
already_exists = False
for child in parent_item.child_items:
if child.asset.name == name and child.asset.type == asset_type:
already_exists = True
if already_exists:
print 'this asset already exists'
return
new_item = TreeItem(asset, "", parent_item)
parent_item.appendChild(new_item)
def removeRow(self, rowIndexes):
child_tree_item = rowIndexes[0].internalPointer()
asset_type = rowIndexes[0].parent().data().toString()
parent_item = self.parents[str(asset_type)]
# hint to keep the tree open after deleting: https://stackoverflow.com/questions/48121393/how-to-delete-multiple-rows-in-a-qtableview-widget
self.beginRemoveRows(QtCore.QModelIndex(), rowIndexes[0].row(), rowIndexes[0].row() + 1)
parent_item.removeChild(child_tree_item)
self.endRemoveRows()
def searchModel(self, asset):
'''
get the modelIndex for a given appointment
'''
def searchNode(node):
'''
a function called recursively, looking at all nodes beneath node
'''
for child in node.child_items:
print child.childCount()
if asset == child.asset:
index = self.createIndex(child.row(), 0, child)
return index
if child.childCount() > 0:
result = searchNode(child)
if result:
return result
retarg = searchNode(self.parents[0])
print retarg
return retarg
def findAssetName(self, name):
app = None
for asset in self.assets:
print asset.name
if asset.name == name:
app = asset
break
if app != None:
index = self.searchModel(app)
return (True, index)
return (False, None)
class TreeView(QtGui.QTreeView):
right_button_clicked = QtCore.pyqtSignal(list, int)
def __init__(self, parent=None):
super(TreeView, self).__init__(parent)
# self.clicked.connect(self.on_treeView_clicked)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.openMenu)
def selectedRows(self):
rows = []
for index in self.selectedIndexes():
if index.column() == 0:
rows.append(index.row())
print type(rows)
return rows
def openMenu(self, position):
indexes = self.selectedIndexes()
if len(indexes) > 0:
level = 0
index = indexes[0]
while index.parent().isValid():
index = index.parent()
level += 1
menu = QtGui.QMenu()
editMenu = None
if level == 0:
editMenu = QtGui.QAction("Edit person", self)
menu.addAction(editMenu)
elif level == 1:
editMenu = QtGui.QAction("Delete", self)
menu.addAction(editMenu)
elif level == 2:
editMenu = QtGui.QAction("Edit object", self)
menu.addAction(editMenu)
if editMenu:
editMenu.triggered.connect(partial(self._on_right_click, indexes, level))
menu.exec_(self.viewport().mapToGlobal(position))
def _on_right_click(self, indexes, level):
self.right_button_clicked.emit(indexes, level)
def delete_test(self):
print 'addButton clicked'
new_asset = ("CHARACTER", "Smith", 'May 28th, 2020')
asset_type, name, date = new_asset
asset = AssetClass(type=asset_type, name=name, date_added=date)
parent_item = self.tree_view.model().parents[asset_type]
already_exists = False
for child in parent_item.child_items:
if child.asset.name == name and child.asset.type == asset_type:
already_exists = True
if already_exists:
print 'this asset already exists'
return
new_item = TreeItem(asset, "", parent_item)
parent_item.appendChild(new_item)
self.tree_view.model().layoutChanged.emit()
class Dialog(QtGui.QDialog):
add_signal = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
self.setMinimumSize(300, 150)
self.model = TreeModel()
layout = QtGui.QVBoxLayout(self)
self.tree_view = TreeView(self)
self.tree_view.setModel(self.model)
self.tree_view.setAlternatingRowColors(True)
self.tree_view.right_button_clicked.connect(self.deleteButtonClicked)
layout.addWidget(self.tree_view)
label = QtGui.QLabel("Search for the following person")
layout.addWidget(label)
buts = []
frame = QtGui.QFrame(self)
layout2 = QtGui.QHBoxLayout(frame)
for asset in self.model.assets:
but = QtGui.QPushButton(asset.name, frame)
buts.append(but)
layout2.addWidget(but)
QtCore.QObject.connect(but, QtCore.SIGNAL("clicked()"), self.but_clicked)
layout.addWidget(frame)
self.add_button = QtGui.QPushButton("Add \"Character - Smith\"")
layout.addWidget(self.add_button)
QtCore.QObject.connect(self.add_button, QtCore.SIGNAL("clicked()"), self.addButtonClicked)
self.delete_button = QtGui.QPushButton("Delete Selected")
layout.addWidget(self.delete_button)
QtCore.QObject.connect(self.delete_button, QtCore.SIGNAL("clicked()"), self.tree_view.clearSelection)
self.but = QtGui.QPushButton("Clear Selection")
layout.addWidget(self.but)
QtCore.QObject.connect(self.but, QtCore.SIGNAL("clicked()"), self.tree_view.clearSelection)
QtCore.QObject.connect(self.tree_view, QtCore.SIGNAL("clicked (QModelIndex)"), self.row_clicked)
def row_clicked(self, index):
'''
when a row is clicked... show the name
'''
print 'row_clicked index type: %s' % index
print self.tree_view.model().data(index, QtCore.Qt.UserRole)
def but_clicked(self):
'''
when a name button is clicked, I iterate over the model,
find the person with this name, and set the treeviews current item
'''
name = self.sender().text()
print "BUTTON CLICKED:", name
result, index = self.model.findAssetName(name)
if result:
if index:
self.tree_view.setCurrentIndex(index)
return
self.tree_view.clearSelection()
def addButtonClicked(self):
print 'addButton clicked'
new_asset = ("CHARACTER", "Smith", 'May 28th, 2020')
self.tree_view.model().addSubRow(new_asset)
self.tree_view.model().layoutChanged.emit()
@QtCore.pyqtSlot(list, int)
def deleteButtonClicked(self, indexes, level):
print 'deleteButton clicked'
self.tree_view.model().removeRow(indexes)
self.tree_view.model().layoutChanged.emit()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
dialog = Dialog()
dialog.show()
sys.exit(app.exec_())
beginRemoveRows()
expects the QModelIndex, which is the parent of the QModelIndex to be removed, as the first parameter. Regarding the example that you indicate in the comments of your code in the table type models, the indexes do not have a parent, so it is passed an invalid QModelIndex.
def removeRow(self, rowIndexes):
child_tree_item = rowIndexes[0].internalPointer()
parent_item = child_tree_item.parent()
self.beginRemoveRows(
rowIndexes[0].parent(), rowIndexes[0].row(), rowIndexes[0].row() + 1
)
parent_item.removeChild(child_tree_item)
self.endRemoveRows()
这篇关于QTreeView QAbstractItemModel 父级在删除项后崩溃,有时会崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!