【问题标题】:InsertRows in QAbstractItemModel with a QSortFilterProxyModel带有 QSortFilterProxyModel 的 QAbstractItemModel 中的 InsertRows
【发布时间】:2020-03-29 14:45:21
【问题描述】:

我正在尝试使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】:

    不必发出 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()
    

    【讨论】:

    • 非常感谢您的回答。方法参数是由于我的 IDE 从 PyQt5 复制参数所致。关键信息是忽略 layoutChanged 以及 layoutAboutToBeChanged 信号。如果没有这些信号,所有错误都会被消除。感谢您的帮助。
    猜你喜欢
    • 2021-07-18
    • 1970-01-01
    • 2020-12-03
    • 2019-07-14
    • 2012-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多