如何使用 beginMoveRows 在 QTableView (QAbstractTableModel) 中移动一行?

问题描述

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

我认为它会因为这个语句而崩溃in the docs...

注意,如果 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,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_())

解决方法

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

但是,在同一父级中向下移动行(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,parent,target)

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

        self.model().endMoveRows()

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