如何使用 QAbstractItemModel 从 QTreeView 中删除行?

Posted

技术标签:

【中文标题】如何使用 QAbstractItemModel 从 QTreeView 中删除行?【英文标题】:How to remove row from QTreeView using QAbstractItemModel? 【发布时间】:2018-05-17 12:05:34 【问题描述】:

我正在尝试从模型中删除 QTreeView 项目“节点 6(删除我)”。

我无法让它工作,因为没有任何内容被删除。我做错了什么? 删除代码见MainWindow.init方法。

import sys

try:
    from PySide2 import QtWidgets
except ImportError:
    from PyQt5 import QtWidgets

try:
    from PySide2 import QtCore
except ImportError:
    from PyQt5 import QtCore


class Node(object):
    def __init__(self, name, parent=None):
        self._name = name
        self._children = []
        self._parent = parent

        if parent is not None:
            parent.addChild(self)

    def addChild(self, child):
        self._children.append(child)
        child._parent = self

    def name(self):
        return self._name

    def child(self, row):
        return self._children[row]

    def insertChild(self, position, child):
        if position < 0 or position > len(self._children):
            return False

        self._children.insert(position, child)
        child._parent = self
        return True

    def childCount(self):
        return len(self._children)

    def parent(self):
        return self._parent

    def setParent(self, new_parent):
        self._parent._children.remove(self)
        self._parent = new_parent
        new_parent._children.append(self)

    def row(self):
        if self._parent is not None:
            return self._parent._children.index(self)

    def removeChild(self, position):
        if position < 0 or position > len(self._children):
            return False
        child = self._children.pop(position)
        child._parent = None
        return True

    def __repr__(self):
        return self._name


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

    def rowCount(self, parent=QtCore.QModelIndex()):
        if not parent.isValid():
            parentNode = self._rootNode
        else:
            parentNode = parent.internalPointer()

        return parentNode.childCount()

    def columnCount(self, parent):
        return 1

    def data(self, index, role):
        """Return whatever the view should display"""

        if not index.isValid():
            return None

        node = index.internalPointer()

        if role == QtCore.Qt.DisplayRole:
            if index.column() == 0:
                return node.name()

    def index(self, row, column, parent):
        if not parent.isValid():
            # parent is not valid when it is the root node, since the "parent"
            # method returns an empty QModelIndex
            parentNode = self._rootNode
        else:
            parentNode = parent.internalPointer()  # the node

        childItem = parentNode.child(row)

        return self.createIndex(row, column, childItem)

    def parent(self, index):
        node = index.internalPointer()

        parentNode = node.parent()

        if parentNode == self._rootNode:
            return QtCore.QModelIndex()

        return self.createIndex(parentNode.row(), 0, parentNode)

    def flags(self, index):

        # Original, inherited flags:
        original_flags = super(TreeModel, self).flags(index)

        return (original_flags | QtCore.Qt.ItemIsEnabled
                | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled
                | QtCore.Qt.ItemIsDropEnabled)

    def headerData(self, section, orientation, role):
        if role == QtCore.Qt.DisplayRole:
            if section == 0:
                return 'Node name'


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)

        self.tree = QtWidgets.QTreeView()
        self.layout = QtWidgets.QGridLayout()
        self.main_widget = QtWidgets.QWidget()

        self.layout.addWidget(self.tree, 0, 0)
        self.main_widget.setLayout(self.layout)
        self.setCentralWidget(self.main_widget)

        # Note how these are added in a non-sorted fashion
        root_node = Node('Hidden root')
        Node(name='Node 3', parent=root_node)  # add using parent param
        n2 = Node(name='Node 2', parent=root_node)  # add using parent param
        Node(name='Node 1', parent=root_node)  # add using parent param
        n2.addChild(Node(name='Node 5'))  # add using addChild
        n2.addChild(Node(name='Node 6 (delete me)'))  # add using addChild
        n2.addChild(Node(name='Node 4'))  # add using addChild

        model = TreeModel(root=root_node)
        self.tree.setModel(model)
        self.tree.expandAll()

        # Attempt to remove a node from within the model
        node2 = model.index(1, 0, QtCore.QModelIndex())  # "Node 2" index
        index = model.index(1, 0, parent=node2)
        index_node = index.internalPointer()
        print('index represents node "%s" (its parent node is "%s")' %
            (index_node.name(), index.parent().internalPointer().name()))
        print('Removing %s on row %s' % (index_node.name(), index.row()))
        model.beginRemoveRows(index.parent(), index.row(), index.row())
        success = model.removeRow(index.row(), parent=index.parent())
        print('Removal was a succes?:', success)
        model.endRemoveRows()


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

【问题讨论】:

您没有实现removeRow 方法,模型不知道如何从您的数据中删除一行... 啊,是的 - 谢谢!! 【参考方案1】:

@Jaa-c 如果您愿意,可以将其复制粘贴到您自己的答案中,我会将其标记为已接受的答案。

import sys

try:
    from PySide2 import QtWidgets
except ImportError:
    from PyQt5 import QtWidgets

try:
    from PySide2 import QtCore
except ImportError:
    from PyQt5 import QtCore


class Node(object):
    def __init__(self, name, parent=None):
        self._name = name
        self._children = []
        self._parent = parent

        if parent is not None:
            parent.addChild(self)

    def addChild(self, child):
        self._children.append(child)
        child._parent = self

    def name(self):
        return self._name

    def child(self, row):
        return self._children[row]

    def insertChild(self, position, child):
        if position < 0 or position > len(self._children):
            return False

        self._children.insert(position, child)
        child._parent = self
        return True

    def childCount(self):
        return len(self._children)

    def parent(self):
        return self._parent

    def setParent(self, new_parent):
        self._parent._children.remove(self)
        self._parent = new_parent
        new_parent._children.append(self)

    def row(self):
        if self._parent is not None:
            return self._parent._children.index(self)

    def removeChild(self, position):
        if position < 0 or position > len(self._children):
            return False
        child = self._children.pop(position)
        child._parent = None
        return True

    def __repr__(self):
        return self._name


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

    def rowCount(self, parent=QtCore.QModelIndex()):
        if not parent.isValid():
            parentNode = self._rootNode
        else:
            parentNode = parent.internalPointer()

        return parentNode.childCount()

    def columnCount(self, parent):
        return 1

    def data(self, index, role):
        """Return whatever the view should display"""

        if not index.isValid():
            return None

        node = index.internalPointer()

        if role == QtCore.Qt.DisplayRole:
            if index.column() == 0:
                return node.name()

    def index(self, row, column, parent):
        if not parent.isValid():
            # parent is not valid when it is the root node, since the "parent"
            # method returns an empty QModelIndex
            parentNode = self._rootNode
        else:
            parentNode = parent.internalPointer()  # the node

        childItem = parentNode.child(row)

        return self.createIndex(row, column, childItem)

    def parent(self, index):
        node = index.internalPointer()

        parentNode = node.parent()

        if parentNode == self._rootNode:
            return QtCore.QModelIndex()

        return self.createIndex(parentNode.row(), 0, parentNode)

    def flags(self, index):

        # Original, inherited flags:
        original_flags = super(TreeModel, self).flags(index)

        return (original_flags | QtCore.Qt.ItemIsEnabled
                | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled
                | QtCore.Qt.ItemIsDropEnabled)

    def headerData(self, section, orientation, role):
        if role == QtCore.Qt.DisplayRole:
            if section == 0:
                return 'Node name'

    def removeRow(self, row, parent):
        if not parent.isValid():
            # parent is not valid when it is the root node, since the "parent"
            # method returns an empty QModelIndex
            parentNode = self._rootNode
        else:
            parentNode = parent.internalPointer()  # the node

        parentNode.removeChild(row)
        return True


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)

        self.tree = QtWidgets.QTreeView()
        self.layout = QtWidgets.QGridLayout()
        self.main_widget = QtWidgets.QWidget()

        self.layout.addWidget(self.tree, 0, 0)
        self.main_widget.setLayout(self.layout)
        self.setCentralWidget(self.main_widget)

        # Note how these are added in a non-sorted fashion
        root_node = Node('Hidden root')
        Node(name='Node 3', parent=root_node)  # add using parent param
        n2 = Node(name='Node 2', parent=root_node)  # add using parent param
        Node(name='Node 1', parent=root_node)  # add using parent param
        n2.addChild(Node(name='Node 5'))  # add using addChild
        n2.addChild(Node(name='Node 6 (delete me)'))  # add using addChild
        n2.addChild(Node(name='Node 4'))  # add using addChild

        model = TreeModel(root=root_node)
        self.tree.setModel(model)
        self.tree.expandAll()

        # Attempt to remove a node from within the model
        node2 = model.index(1, 0, QtCore.QModelIndex())  # "Node 2" index
        index = model.index(1, 0, parent=node2)
        index_node = index.internalPointer()
        print('index represents node "%s" (its parent node is "%s")' %
            (index_node.name(), index.parent().internalPointer().name()))
        print('Removing %s on row %s' % (index_node.name(), index.row()))
        model.beginRemoveRows(index.parent(), index.row(), index.row())
        success = model.removeRow(index.row(), parent=index.parent())
        print('Removal was a succes?:', success)
        model.endRemoveRows()


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

【讨论】:

如果你有时间,我也实现了moveRow,但虽然移动行工作但出现错误:***.com/questions/50392221/…

以上是关于如何使用 QAbstractItemModel 从 QTreeView 中删除行?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 QAbstractItemModel 和 QTreeView 类中找到子项?

如何在 QAbstractItemModel 中为 QTreeView 创建人工节点

应用于自定义 QAbstractItemModel 的外部选择

QAbstractItemModel data() 永远不会被调用

以编程方式检查QAbstractItemModel / QTreeView中的项目

QTreeView 的 QAbstractItemModel:我做错了啥?