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

How can I move a row in a QTableView (QAbstractTableModel) using beginMoveRows?

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

我认为它崩溃是因为这条语句 in the docs...

Note that if sourceParent and destinationParent are the same, you must ensure that the destinationChild is not within the range of sourceFirst and sourceLast + 1. You must also ensure that you do not attempt to move a row to one of its own children or ancestors. This method returns false if either condition is true, in which case you should abort your move operation.

但是这个限制是不是意味着我不能将一行向下移动 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_())

您应该了解文档的另一部分:

However, when moving rows down in the same parent (sourceParent and destinationParent are equal), the rows will be placed before the destinationChild index. That is, if you wish to move rows 0 and 1 so they will become rows 1 and 2, destinationChild should be 3. In this case, the new index for the source row i (which is between sourceFirst and sourceLast) is equal to (destinationChild-sourceLast-1+i).

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

在您的情况下,由于您移动的是单行,因此解决方案很简单:如果移动到底部则加 1。另请注意,您应该使用 parent,对于 table 模型,它始终是无效的 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 的模块化,您应该在模型 class 而不是视图中创建适当的函数。