QTreeView запрашивает индекс для недопустимой строки

Взгляните на следующий 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()

Насколько я могу судить, реализация моей модели правильная (хотя и очень простая). В частности, количество строк и столбцов, о которых он сообщает, является правильным, и он никогда не создает индексы для данных, которые были бы недействительными.

Шаги для воспроизведения моей проблемы:

  • Запустите код выше.
  • В дереве выберите пункт 2.
  • Нажмите кнопку Удалить.

В моей системе приложение вылетает в beginRemoveRows(), потому что представление запрашивает QModelIndex для строки 2. Естественно, строки 2 не существует.

Любая идея, почему QTreeView думает, что будет 3 строки, когда модель явно сообщает, что их только 2?

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
0
686
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Когда элемент добавляется, перемещается, удаляется и т. д., модель проверяет, является ли индекс 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])

Это не может быть проблемой здесь. Обратите внимание, что ошибка возникает в beginRemoveRows, т.е. до были внесены какие-либо изменения в модель. Кроме того, мне не нужно беспокоиться о постоянных индексах, это как раз и есть цель beginRemoveRows и endRemoveRows иметь дело с постоянством.

polwel 22.05.2019 20:16

@polwel Вы не ошиблись. Никакой ошибки там нет, попробуйте мое решение и вы убедитесь, что я прав. Я рекомендую вам проверить официальный пример Qt: doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.h‌​tml И если вы хотите копнуть глубже, проанализируйте исходный код: code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/itemmodels/…

eyllanesc 22.05.2019 20:20

Хорошо, меня смутил заявление в документах: «Обратите внимание, что передача недопустимых индексов в модели элементов является неопределенным поведением, поэтому приложения должны воздерживаться от этого и не полагаться на какое-либо «защитное» программирование, которое модели элементов могут использовать для корректной обработки недопустимых индексов». Я понял, что эта «узкая» парадигма также включает index(). По-видимому, это не так. QTreeView не проверяет, является ли row+1 законным при вызове index(), так что это должна обрабатывать модель.

polwel 22.05.2019 20:28

@polwel Именно, Qt указывает, что вы должны проверить, поскольку большинство методов не защищены от ошибок такого рода. Из-за эффективности и сложности Qt этого не делает.

eyllanesc 22.05.2019 20:31

Да, в документах это упоминается где-то: Если не найден допустимый дочерний элемент, соответствующий указанной строке, столбцу и индексу родительской модели, [index()] должен возвращать QModelIndex() [...]

polwel 22.05.2019 20:34

@polwel Точно, на самом деле в моем случае я предпочитаю анализировать, как реализован QStandardItemModel, поскольку это универсальная модель. :-)

eyllanesc 22.05.2019 20:36

Нет, Qt указывает, что модель должна для производительности нет делать это. Это ответственность взгляда. Очевидно, что index() является исключением из этого правила. Спасибо за вашу помощь! Я принял твой ответ. Редактировать: не видел ваш последний комментарий перед публикацией этого.

polwel 22.05.2019 20:37

Давайте продолжить обсуждение в чате.

polwel 22.05.2019 20:39

Другие вопросы по теме