InsertRows в QAbstractItemModel с QSortFilterProxyModel - PullRequest
1 голос
/ 28 марта 2020

Я пытаюсь заставить QTreeView работать с QSortFilterProxyModel . Я написал минимальный рабочий пример (который, к сожалению, не настолько минимален из-за сложности вопроса). Полный код:

import logging
from PyQt5 import QtCore, QtWidgets
import sys


class DBObject:
    def __init__(self, name, parent, children=None):
        self.name = name
        self.parent = parent
        self.children = children or list()

    def __repr__(self):
        return f"name: {self.name}, parent: {self.parent.name if self.parent is not None else '-'}"


class Model(QtCore.QAbstractItemModel):
    def __init__(self, root, parent=None):
        super().__init__(parent)

        self._root = root

    def columnCount(self, parent=None, *args, **kwargs):
        return 1

    def rowCount(self, parent=None, *args, **kwargs):
        if not parent.isValid():
            return 1

        parentItem = parent.internalPointer()
        rowCount = len(parentItem.children)
        logging.info(f"rowCount({parentItem}): rowCount={rowCount}")
        return rowCount

    def parent(self, index):
        if not index.isValid():
            return QtCore.QModelIndex()

        item = index.internalPointer()
        parentItem = item.parent

        logging.info(f"parent({item}): parent={parentItem}")
        if parentItem is None:
            return QtCore.QModelIndex()
        else:
            if parentItem.parent is None:
                return self.createIndex(0, 0, parentItem)
            else:
                return self.createIndex(parentItem.parent.children.index(parentItem), 0, parentItem)

    def index(self, row, column, parent=None, *args, **kwargs):
        if not parent.isValid():
            if row != 0 or column != 0:
                return QtCore.QModelIndex()
            else:
                logging.info(f"index({row}, {column}, None): index={self._root}")
                return self.createIndex(0, 0, self._root)

        parentItem = parent.internalPointer()

        if 0 <= row < len(parentItem.children):
            logging.info(f"index({row}, {column}, {parentItem}): index={parentItem.children[row]}")
            return self.createIndex(row, column, parentItem.children[row])
        else:
            logging.info(f"index({row}, {column}, {parentItem}): index=None")
            return QtCore.QModelIndex()

    def data(self, index, role=None):
        if not index.isValid():
            return QtCore.QVariant()

        item = index.internalPointer()

        if role == QtCore.Qt.DisplayRole:
            return item.name
        else:
            return QtCore.QVariant()

    def flags(self, index):
        if not index.isValid():
            return QtCore.Qt.NoItemFlags

        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable

    def setData(self, index, value, role=None):
        if not index.isValid():
            return False

        item = index.internalPointer()

        if role == QtCore.Qt.EditRole:
            item.name = value

            self.dataChanged.emit(index, index, [role])

            return True
        else:
            return False

    def insertRows(self, row, count, parent):
        self.beginInsertRows(parent, row, row + count - 1)

        parentItem = parent.internalPointer()

        for i in range(count):
            parentItem.children.append(DBObject("new", parentItem))

        self.endInsertRows()

        self.layoutChanged.emit()

        return True


class ProxyModel(QtCore.QSortFilterProxyModel):
    def __init__(self, root, parent=None):
        super().__init__(parent)

        self._root = root


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, root):
        super().__init__()

        self._root = root

        self.setMinimumSize(640, 480)

        centralWidget = QtWidgets.QWidget(self)
        self.setCentralWidget(centralWidget)

        layout = QtWidgets.QVBoxLayout(centralWidget)

        self._treeView = QtWidgets.QTreeView(self)
        layout.addWidget(self._treeView)

        self._model = Model(self._root, self)
        self._proxyModel = ProxyModel(self._root, self)
        self._proxyModel.setSourceModel(self._model)
        self._treeView.setModel(self._proxyModel)

        self._treeView.expandAll()

        button = QtWidgets.QPushButton("Add")
        layout.addWidget(button)

        button.clicked.connect(self._Clicked)

    def _Clicked(self):
        self._model.insertRow(len(self._root.children), self._model.index(0, 0, QtCore.QModelIndex()))

        self._model.layoutChanged.emit()
        self._treeView.expandAll()


def main():
    root = DBObject("root", None)

    items = ["foo", "bar", "baz"]
    for x in items:
        child = DBObject(x + "0", root)
        root.children.append(child)

        for y in items:
            child.children.append(DBObject(y + "1", child))

    app = QtWidgets.QApplication(sys.argv)

    mainWindow = MainWindow(root)
    mainWindow.show()

    app.exec_()


if __name__ == "__main__":
    main()

До сих пор меня интересовало только отображение доступных данных, и я мог выбирать и даже редактировать отображаемые данные. Однако теперь я также хотел бы добавить данные в модель, но мне это не удалось.

Чтобы добавить данные, мне нужно перегрузить метод insertRows , приведенный ниже, и у меня есть пара вопросов по этому поводу:

def insertRows(self, row, count, parent):
    self.beginInsertRows(parent, row, row + count - 1)

    parentItem = parent.internalPointer()

    for i in range(count):
        parentItem.children.append(DBObject("new", parentItem))

    self.endInsertRows()
    self.layoutChanged.emit()

    return True

  1. Использует ли этот метод нужно быть в производном классе QAbstractItemModel или QSortFilterProxyModel?
  2. Нужно ли использовать createIndex внутри этого метода?
  3. Нужно ли вызывать layoutAboutToBeChanged внутри этого метода?

Проблема возникает, когда я начинаю выбирать различные элементы в древовидном представлении, а также добавлять новые данные. Отказы варьируются от ошибок сегментации до QSortFilterProxyModel: index from wrong model passed to mapFromSource.

. Я подавал свою модель через набор тестов, найденный здесь , и все в порядке. Используемая мной QSortFilterProxyModel не выполняет никакой фильтрации или сортировки в этом примере, поэтому проблема не связана с этим.

Любая помощь, подсказка или обратная связь приветствуются.

1 Ответ

1 голос
/ 29 марта 2020

Нет необходимости излучать сигнал layoutChanged. Другая ошибка заключается в том, что метод index() устанавливает для родительского параметра по умолчанию значение None вместо QModelIndex. Также я вижу ненужным использование * args и ** kwargs в методах, которые его не используют и уже имеют предопределенное поведение и аргументы.

import logging
import sys

from PyQt5 import QtCore, QtWidgets


class DBObject:
    def __init__(self, name, parent, children=None):
        self.name = name
        self.parent = parent
        self.children = children or list()

    def __repr__(self):
        return f"name: {self.name}, parent: {self.parent.name if self.parent is not None else '-'}"


class Model(QtCore.QAbstractItemModel):
    def __init__(self, root, parent=None):
        super().__init__(parent)

        self._root = root

    def columnCount(self, parent=QtCore.QModelIndex()):
        return 1

    def rowCount(self, parent=QtCore.QModelIndex()):
        if not parent.isValid():
            return 1

        parentItem = parent.internalPointer()
        rowCount = len(parentItem.children)
        logging.info(f"rowCount({parentItem}): rowCount={rowCount}")
        return rowCount

    def parent(self, index):
        if not index.isValid():
            return QtCore.QModelIndex()

        item = index.internalPointer()
        parentItem = item.parent

        logging.info(f"parent({item}): parent={parentItem}")
        if parentItem is None:
            return QtCore.QModelIndex()
        else:
            if parentItem.parent is None:
                return self.createIndex(0, 0, parentItem)
            else:
                return self.createIndex(
                    parentItem.parent.children.index(parentItem), 0, parentItem
                )

    def index(self, row, column, parent=QtCore.QModelIndex()):
        if not parent.isValid():
            if row != 0 or column != 0:
                return QtCore.QModelIndex()
            else:
                logging.info(f"index({row}, {column}, None): index={self._root}")
                return self.createIndex(0, 0, self._root)

        parentItem = parent.internalPointer()

        if 0 <= row < len(parentItem.children):
            logging.info(
                f"index({row}, {column}, {parentItem}): index={parentItem.children[row]}"
            )
            return self.createIndex(row, column, parentItem.children[row])
        else:
            logging.info(f"index({row}, {column}, {parentItem}): index=None")
            return QtCore.QModelIndex()

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if not index.isValid():
            return QtCore.QVariant()

        item = index.internalPointer()

        if role == QtCore.Qt.DisplayRole:
            return item.name
        else:
            return QtCore.QVariant()

    def flags(self, index):
        if not index.isValid():
            return QtCore.Qt.NoItemFlags

        return (
            QtCore.Qt.ItemIsEnabled
            | QtCore.Qt.ItemIsEditable
            | QtCore.Qt.ItemIsSelectable
        )

    def setData(self, index, value, role=QtCore.Qt.EditRole):
        if not index.isValid():
            return False

        item = index.internalPointer()

        if role == QtCore.Qt.EditRole:
            item.name = value

            self.dataChanged.emit(index, index, [role])

            return True
        else:
            return False

    def insertRows(self, row, count, parent):
        self.beginInsertRows(parent, row, row + count - 1)

        parentItem = parent.internalPointer()

        for i in range(count):
            parentItem.children.append(DBObject("new", parentItem))
        self.endInsertRows()
        return True


class ProxyModel(QtCore.QSortFilterProxyModel):
    def __init__(self, root, parent=None):
        super().__init__(parent)

        self._root = root


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, root):
        super().__init__()

        self._root = root

        self.setMinimumSize(640, 480)

        centralWidget = QtWidgets.QWidget(self)
        self.setCentralWidget(centralWidget)

        layout = QtWidgets.QVBoxLayout(centralWidget)

        self._treeView = QtWidgets.QTreeView(self)
        layout.addWidget(self._treeView)

        self._model = Model(self._root, self)
        self._proxyModel = ProxyModel(self._root, self)
        self._proxyModel.setSourceModel(self._model)
        self._treeView.setModel(self._proxyModel)

        self._treeView.expandAll()

        button = QtWidgets.QPushButton("Add")
        layout.addWidget(button)

        button.clicked.connect(self._Clicked)

    def _Clicked(self):
        self._model.insertRow(
            len(self._root.children), self._model.index(0, 0, QtCore.QModelIndex())
        )
        self._treeView.expandAll()


def main():
    root = DBObject("root", None)

    items = ["foo", "bar", "baz"]
    for x in items:
        child = DBObject(x + "0", root)
        root.children.append(child)

        for y in items:
            child.children.append(DBObject(y + "1", child))

    app = QtWidgets.QApplication(sys.argv)

    mainWindow = MainWindow(root)
    mainWindow.show()

    app.exec_()


if __name__ == "__main__":
    main()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...