Взгляните на следующий MWE.
Это простой QAbstractItemModel только с одним уровнем, элементы которого хранятся в списке. Я создаю QTreeView для отображения модели и кнопку для удаления второго элемента.
from PyQt5.QtCore import QModelIndex, QAbstractItemModel, Qt
from PyQt5.QtWidgets import QTreeView, QApplication, QPushButton
class Item:
def __init__(self, title):
self.title = title
class TreeModel(QAbstractItemModel):
def __init__(self, parent=None):
super().__init__(parent)
self._items = [] # typing.List[Item]
def addItem(self, item: Item):
self.beginInsertRows(QModelIndex(), len(self._items), len(self._items))
self._items.append(item)
self.endInsertRows()
def removeItem(self, item: Item):
index = self._items.index(item)
self.beginRemoveRows(QModelIndex(), index, index)
self._items.remove(item)
self.endRemoveRows()
# ----- overridden methods from QAbstractItemModel -----
# noinspection PyMethodOverriding
def data(self, index: QModelIndex, role):
item = index.internalPointer()
if role == Qt.DisplayRole:
return item.title
# noinspection PyMethodOverriding
def rowCount(self, parent=QModelIndex()):
if not parent.isValid():
return len(self._items)
return 0
# noinspection PyMethodOverriding
def columnCount(self, parent=QModelIndex()):
return 1
# noinspection PyMethodOverriding
def index(self, row: int, col: int, parent=QModelIndex()):
assert not parent.isValid()
return self.createIndex(row, 0, self._items[row])
def parent(self, index=QModelIndex()):
return QModelIndex()
def removeItem():
model.removeItem(item2)
if __name__ == '__main__':
app = QApplication([])
model = TreeModel()
button = QPushButton('Delete')
button.clicked.connect(removeItem)
button.show()
item1 = Item('Item 1')
model.addItem(item1)
item2 = Item('Item 2')
model.addItem(item2)
treeView = QTreeView()
treeView.setModel(model)
treeView.show()
app.exec()
Насколько я могу судить, реализация моей модели правильная (хотя и очень простая). В частности, количество строк и столбцов, о которых он сообщает, является правильным, и он никогда не создает индексы для данных, которые были бы недействительными.
Шаги для воспроизведения моей проблемы:
В моей системе приложение вылетает в beginRemoveRows(), потому что представление запрашивает QModelIndex для строки 2. Естественно, строки 2 не существует.
Любая идея, почему QTreeView думает, что будет 3 строки, когда модель явно сообщает, что их только 2?






Когда элемент добавляется, перемещается, удаляется и т. д., модель проверяет, является ли индекс QPersistentModelIndex действительным или нет, поэтому она вызывает метод показатель() класса QAbstractItemModel. И в этом методе разработчик несет ответственность за проверку правильности строки или столбца, и для этого модель предоставляет метод имеет индекс (), который вы не используете, вызывая указанную вами ошибку, поэтому решение:
def index(self, row: int, col: int, parent=QModelIndex()):
if not self.hasIndex(row, col, parent):
return QModelIndex()
assert not parent.isValid()
return self.createIndex(row, 0, self._items[row])
@polwel Вы не ошиблись. Никакой ошибки там нет, попробуйте мое решение и вы убедитесь, что я прав. Я рекомендую вам проверить официальный пример Qt: doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html И если вы хотите копнуть глубже, проанализируйте исходный код: code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/itemmodels/…
Хорошо, меня смутил заявление в документах: «Обратите внимание, что передача недопустимых индексов в модели элементов является неопределенным поведением, поэтому приложения должны воздерживаться от этого и не полагаться на какое-либо «защитное» программирование, которое модели элементов могут использовать для корректной обработки недопустимых индексов». Я понял, что эта «узкая» парадигма также включает index(). По-видимому, это не так. QTreeView не проверяет, является ли row+1 законным при вызове index(), так что это должна обрабатывать модель.
@polwel Именно, Qt указывает, что вы должны проверить, поскольку большинство методов не защищены от ошибок такого рода. Из-за эффективности и сложности Qt этого не делает.
Да, в документах это упоминается где-то: Если не найден допустимый дочерний элемент, соответствующий указанной строке, столбцу и индексу родительской модели, [index()] должен возвращать QModelIndex() [...]
@polwel Точно, на самом деле в моем случае я предпочитаю анализировать, как реализован QStandardItemModel, поскольку это универсальная модель. :-)
Нет, Qt указывает, что модель должна для производительности нет делать это. Это ответственность взгляда. Очевидно, что index() является исключением из этого правила. Спасибо за вашу помощь! Я принял твой ответ. Редактировать: не видел ваш последний комментарий перед публикацией этого.
Давайте продолжить обсуждение в чате.
Это не может быть проблемой здесь. Обратите внимание, что ошибка возникает в
beginRemoveRows, т.е. до были внесены какие-либо изменения в модель. Кроме того, мне не нужно беспокоиться о постоянных индексах, это как раз и есть цельbeginRemoveRowsиendRemoveRowsиметь дело с постоянством.