Qt endRemoveRows() 关于无效索引的警告
Qt endRemoveRows() warning about invalid index
我的程序有一个链接到允许添加和删除元素的模型的树视图。当当前选择的项目发生变化时,我必须执行与新选择的项目相关的操作(例如显示图像)。作为更改信号,我使用 selectionModel()
中的 currentChanged()
。一切正常,除了一种情况:当 parent 有多个 child 并且我删除了第 0 行时,endRemoveRows() 警告我无效索引 (-1,0) 和新的 0 行视图的 未正确突出显示为选中:但是,正确执行了对该项目的操作。
更一般地说,我相信在删除一个项目后,currentChanged() 信号总是与删除前引用模型的索引一起发出,这会在我删除第一行时导致视图出现问题。
如何避免 endRemoveRows() 关于无效索引的警告并确保正确突出显示第 0 行的新项目?
模型中的三个兄弟姐妹:第 0、1、2 行的 image0、image1、image2。
- 测试 1) Image2 在树视图中为青色。如果我删除 image2 :
- 新索引有第 1 行
- 在树视图中选择了图像 1 (但灰色)
- 测试 2) Image0 在树视图中为青色。如果我删除 Image0:
- 关于无效索引 (-1,0) 的 endRemoveRows 警告
- 新索引有第 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_())
我找到了问题的答案。在上面的示例中,endRemoveRows()
在以下两种情况发生时发出有关无效索引的警告:
- 您想删除第 0 行子项(示例中的 Image0)
- 在视图中或关联的
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
中:由于这样做,要求模型 return 与该索引关联的对象将 return None
编辑
Since no item in the view is selected, every click I make produces a currentChanged(). So if I click on the new 0 row I get that the related print is executed again
这可以通过以下代码避免
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)
目前所选项目的颜色为灰色而不是青色:我会在解决后进行编辑,但这是次要方面。题目是去掉invalid index warning自动选择一行,不会重复执行代码,这个答案的代码符合我的要求。
我的程序有一个链接到允许添加和删除元素的模型的树视图。当当前选择的项目发生变化时,我必须执行与新选择的项目相关的操作(例如显示图像)。作为更改信号,我使用 selectionModel()
中的 currentChanged()
。一切正常,除了一种情况:当 parent 有多个 child 并且我删除了第 0 行时,endRemoveRows() 警告我无效索引 (-1,0) 和新的 0 行视图的 未正确突出显示为选中:但是,正确执行了对该项目的操作。
更一般地说,我相信在删除一个项目后,currentChanged() 信号总是与删除前引用模型的索引一起发出,这会在我删除第一行时导致视图出现问题。
如何避免 endRemoveRows() 关于无效索引的警告并确保正确突出显示第 0 行的新项目?
模型中的三个兄弟姐妹:第 0、1、2 行的 image0、image1、image2。
- 测试 1) Image2 在树视图中为青色。如果我删除 image2 :
- 新索引有第 1 行
- 在树视图中选择了图像 1 (但灰色)
- 测试 2) Image0 在树视图中为青色。如果我删除 Image0:
- 关于无效索引 (-1,0) 的 endRemoveRows 警告
- 新索引有第 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_())
我找到了问题的答案。在上面的示例中,endRemoveRows()
在以下两种情况发生时发出有关无效索引的警告:
- 您想删除第 0 行子项(示例中的 Image0)
- 在视图中或关联的
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
中:由于这样做,要求模型 return 与该索引关联的对象将 return None
编辑
Since no item in the view is selected, every click I make produces a currentChanged(). So if I click on the new 0 row I get that the related print is executed again
这可以通过以下代码避免
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)
目前所选项目的颜色为灰色而不是青色:我会在解决后进行编辑,但这是次要方面。题目是去掉invalid index warning自动选择一行,不会重复执行代码,这个答案的代码符合我的要求。