Qt endRemoveRows()警告无效索引

3
我的程序有一个树形视图,与允许添加和删除元素的模型相连。当当前选定项更改时,我必须执行与新选定项相关的操作(例如显示图像)。我使用selectionModel()中的currentChanged()作为更改信号。除了一种情况外,一切正常:当父项有多个子项并且我删除行0时,endRemoveRows()会警告我无效索引(-1,0),并且视图的新0行未正确突出显示为已选择:但是,对该项的操作被正确执行。 更一般地说,我认为在删除项目后,currentChanged()信号总是发出指向删除前的模型的索引,这会导致在我删除第一行时在视图中出现问题。
如何避免endRemoveRows()警告无效索引,并确保位于行0的新项目正确突出显示?
模型中有三个兄弟:image0、image1和image2,在0、1、2行。
  • 测试1) 在树形视图中,Image2为青色。如果我删除Image2:
    • 新索引行为1
    • 在树形视图中,Image1被选中(但是是灰色的)
  • 测试2) 在树形视图中,Image0为青色。如果我删除Image0:
    • endRemoveRows警告无效索引(-1,0)
    • 新索引行为1
    • 打印出新的0行项目,但是视图中没有任何项目被选中(甚至不是灰色的)
    • 由于视图中没有选择任何项目,所以我每次点击都会产生currentChanged()。所以如果我点击新的0行,就会再次执行相关的打印操作

最小工作示例

如果您在pycharm上运行以下代码,请转到编辑配置并激活“在输出控制台中模拟终端”。

from PyQt5.QtGui import QStandardItemModel
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QStandardItem
from PyQt5.QtCore import Qt
from PyQt5 import QtWidgets, uic, QtCore



class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.treeView = QtWidgets.QTreeView(self.centralwidget)
        self.treeView.setGeometry(QtCore.QRect(140, 100, 241, 391))
        self.treeView.setObjectName("treeView")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(180, 60, 151, 23))
        self.pushButton.setObjectName("pushButton")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton.setText(_translate("MainWindow", "deleteSelectedItem"))

    def mySetup(self):
        self.myModel = QStandardItemModel(0,1)
        self.myModel.setHeaderData(0, Qt.Horizontal, "File name")
        self.myModel.parent = self
        image0 = QStandardItem()
        image0.setText("image0.png")
        image1 = QStandardItem()
        image1.setText("image1.png")
        image2 = QStandardItem()
        image2.setText("image2.png")

        folder = QStandardItem()
        folder.setText("folderA")
        folder.appendRows([image0,image1,image2])
        self.myModel.appendRow(folder)

        self.treeView.setModel(self.myModel)
        self.treeView.selectionModel().currentChanged.connect(self.model_current_item_changed)
        self.pushButton.clicked.connect(self.erase_selected_treeitem)



    def model_current_item_changed(self, new, old):
        print("new:", new.row())
        print("old", old.row())
        #print("current:", self.treeView.selectionModel().currentIndex().row())

        if new.isValid() and not self.treeView.model().itemFromIndex(new).hasChildren():  # show item image
            print("Is image:" , self.treeView.model().itemFromIndex(new).text())
        else:
            print("Is Folder:", self.treeView.model().itemFromIndex(new).text())

    def erase_selected_treeitem(self):
        index = self.treeView.selectionModel().currentIndex()
        if index is not None and index.isValid():
            parent = index.parent()
            self.myModel.beginRemoveRows(parent, index.row(), index.row())
            self.myModel.removeRow(index.row(), parent)
            self.myModel.endRemoveRows()



if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    ui.mySetup()
    MainWindow.show()
    sys.exit(app.exec_())

请提供一个最小可复现的示例(MRE)。 - undefined
@eyllanesc 好的,我会做的,但是为了简化事情,我需要写一些代码(当然除了已经显示的那部分)。 - undefined
1
这正是你的工作,接下来的几次机会你必须从头开始做。 - undefined
@eyllanesc 好的,谢谢你的耐心。我现在要加载它了。 - undefined
@eyllanesc 完成 - undefined
这个问题发生在我身上,是由于我子类实现的QAbstractItemModel.createIndex中的一个bug导致的。即别名,即为两个不同的元素返回相同的内部指针/ID,在删除过程中导致了这个警告。一旦我修复了它,警告就消失了。 - undefined
1个回答

2
我找到了答案。在上面的例子中,当同时发生以下两种情况时:endRemoveRows() 会发出关于无效索引的警告:
  1. 您想要删除0行子项(例如示例中的Image0)
  2. 视图或相关的 view.selectionModel() 中选择了任何项目

selectionModel 包含当前选定项目的索引。当执行修改链接到视图的模型的函数(例如删除行)时,该索引不会自动更新并可能变为无效。在删除项目之前显式调用 selectionModel().clear() 可以解决这个问题。

def erase_selected_treeitem(self):
    index = self.treeView.selectionModel().currentIndex()
    if index is not None and index.isValid():
        parent = index.parent()
        self.treeView.selectionModel().clear()
        self.myModel.beginRemoveRows(parent, index.row(), index.row())
        self.myModel.removeRow(index.row(), parent)
        self.myModel.endRemoveRows()

def model_current_item_changed(self, new, old):
        print("new:", new.row())
        print("old", old.row())
    

如您所见,model_current_item_changed(self, new, old) 也发生了变化:这是因为在新的 erase_selected_treeitem 中,当执行 currentChanged() 时,与已删除项目相关联的 QModelIndex 不存在,甚至不在 selectionModel 中:由于此原因,要求模型返回与该索引关联的对象将返回 None

编辑

由于视图中没有选择任何项,因此我进行的每次单击都会产生 currentChanged()。 因此,如果我单击新的第0行,则会再次执行相关的打印。

可以使用以下代码避免这种情况

def erase_selected_treeitem(self):
    index = self.treeView.selectionModel().currentIndex()
    row =index.row()
    if index is not None and index.isValid():
        parent = index.parent()
        self.treeView.selectionModel().clear()
        self.myModel.beginRemoveRows(parent, index.row(), index.row())
        self.myModel.removeRow(index.row(), parent)
        self.myModel.endRemoveRows()

        #select sibling of the deleted item
        if index.siblingAtRow(index.row()-1).isValid():
            self.treeView.selectionModel().setCurrentIndex(index.siblingAtRow(index.row()-1), QItemSelectionModel.ClearAndSelect)
        elif index.siblingAtRow(index.row()).isValid():
            self.treeView.selectionModel().setCurrentIndex(index.siblingAtRow(index.row()), QItemSelectionModel.ClearAndSelect)

目前选定的项目是灰色而不是青色:我会在解决它时进行编辑,但这只是一个次要问题。问题是如何去除无效索引警告并自动选择一行,不可能出现代码重复执行的情况,而此答案中的代码符合我的要求。


谢谢!我在这个问题上走了一条兔子洞。你带我找到了真正的问题。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接