带有QSortFilterProxyModel的QAbstractItemModel中的InsertRows

问题描述 投票:0回答:1

我正在尝试使QTreeViewQSortFilterProxyModel一起使用。我写了一个最小的工作示例(不幸的是,由于问题的复杂性,它并不是那么简单)。完整的代码是:

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

我从模型测试套件中发现here来填充模型,并且一切正常。在此示例中,我使用的QSortFilterProxyModel不执行任何过滤或排序,因此问题与此无关。

感谢任何帮助,提示或反馈。

python model-view-controller pyqt pyqt5
1个回答
0
投票

不必发出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()
© www.soinside.com 2019 - 2024. All rights reserved.