首页 > 解决方案 > 如何使用 beginMoveRows 在 QTableView (QAbstractTableModel) 中移动一行?

问题描述

我试图在下面的示例中移动我的 QTableView 中的行,但我很难理解如何正确调用beginMoveRows. 我的示例有 3 个按钮来执行各种行移动,而第三个按钮(将行向下移动 1)会导致我的程序崩溃。

我认为它崩溃是因为文档中的这个声明......

请注意,如果 sourceParent 和destinationParent 相同,则必须确保destinationChild 不在sourceFirst 和sourceLast + 1 的范围内。还必须确保不要尝试将行移动到其自己的子级或祖先之一。如果任一条件为真,则此方法返回 false,在这种情况下,您应该中止移动操作。

但是这个限制不是意味着我不能将一行向下移动 1 吗?在我的情况下 sourceLast==sourceFirst 因为我一次只移动一行所以这个语句基本上说我的目标索引不能等于我的源索引 + 1,这是将行向下移动 1 的定义。我是误解这些论点是什么意思?在此示例中,如何将行向下移动 1 个位置?

from PyQt5 import QtWidgets, QtCore, QtGui
import sys

from PyQt5.QtCore import QModelIndex, Qt


class MyTableModel(QtCore.QAbstractTableModel):
    def __init__(self, data=[[]], parent=None):
        super().__init__(parent)
        self.data = data

    def headerData(self, section: int, orientation: Qt.Orientation, role: int):
        if role == QtCore.Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                return "Column " + str(section)
            else:
                return "Row " + str(section)

    def columnCount(self, parent=None):
        return len(self.data[0])

    def rowCount(self, parent=None):
        return len(self.data)

    def data(self, index: QModelIndex, role: int):
        if role == QtCore.Qt.DisplayRole:
            row = index.row()
            col = index.column()
            return str(self.data[row][col])


class MyTableView(QtWidgets.QTableView):
    def __init__(self, parent=None):
        super().__init__(parent)

    def move_row(self, ix, new_ix):
        full_index = self.model().index(0, self.model().rowCount())
        self.model().beginMoveRows(full_index, ix, ix, full_index, new_ix)

        data = self.model().data
        data.insert(new_ix, data.pop(ix))

        self.model().endMoveRows()


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)

    data = [[11, 12, 13, 14, 15],
            [21, 22, 23, 24, 25],
            [31, 32, 33, 34, 35],
            [41, 42, 43, 44, 45],
            [51, 52, 53, 54, 55],
            [61, 62, 63, 64, 65]]

    model = MyTableModel(data)
    view = MyTableView()
    view.setModel(model)

    container = QtWidgets.QWidget()
    layout = QtWidgets.QVBoxLayout()
    container.setLayout(layout)
    layout.addWidget(view)

    button = QtWidgets.QPushButton("Move 3rd row down by 2")
    button.clicked.connect(lambda: view.move_row(2, 2 + 2))
    layout.addWidget(button)

    button = QtWidgets.QPushButton("Move 3rd row up by 1")
    button.clicked.connect(lambda: view.move_row(2, 2 - 1))
    layout.addWidget(button)

    button = QtWidgets.QPushButton("Move 3rd row down by 1 (This fails)")
    button.clicked.connect(lambda: view.move_row(2, 2 + 1))
    layout.addWidget(button)

    container.show()
    sys.exit(app.exec_())

标签: pythonpyqtqtableviewqabstracttablemodel

解决方案


您应该注意文档的另一部分:

但是,当在同一个父项中向下移动行时(sourceParent 和destinationParent 相等),这些行将放置在destinationChild 索引之前。也就是说,如果您希望移动第 0 行和第 1 行,使其变为第 1 行和第 2 行,destinationChild 应该为 3。在这种情况下,源行 i(位于 sourceFirst 和 sourceLast 之间)的新索引等于 ( destinationChild-sourceLast-1+i)。

您的解决方案没有考虑到这一点,您可以清楚地看到,如果您选择第三行然后尝试使用第一个按钮:当数据正确移动时,选择不是。

在您的情况下,由于您要移动单行,因此解决方案很简单:如果移动到底部,则添加 1。另请注意,您应该使用parent,对于表模型,它始终是无效的 QModelIndex。

    def move_row(self, ix, new_ix):
        parent = QtCore.QModelIndex()
        if new_ix > ix:
            target = new_ix + 1
        else:
            target = new_ix
        self.model().beginMoveRows(parent, ix, ix, parent, target)

        data = self.model().data
        data.insert(new_ix, data.pop(ix))

        self.model().endMoveRows()

考虑到,为了更好地符合 OOP 的模块化,您应该在模型类中创建适当的函数,而不是在视图中。


推荐阅读