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

Posted

技术标签:

【中文标题】如何使用 beginMoveRows 在 QTableView (QAbstractTableModel) 中移动一行?【英文标题】:How can I move a row in a QTableView (QAbstractTableModel) using beginMoveRows? 【发布时间】:2021-08-27 19:50:37 【问题描述】:

我试图在下面的示例中移动我的 QTableView 中的行,但我很难理解如何正确调用 beginMoveRows。我的示例有 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, 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_())

【问题讨论】:

【参考方案1】:

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

但是,当在同一个父级中向下移动行时(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 的模块化,您应该在模型类中创建适当的函数,而不是在视图中。

【讨论】:

谢谢!我读了那段但误解了,感谢您的帮助 嗨,你能看看我编辑的问题吗?我最终遇到了一个类似的障碍,将完全相同的东西应用于列而不是行,但不知道为什么。我不想提出一个与我的第一个问题如此相似的新问题 @Esostack 不,不!你不能完全改变一个问题,尤其是在给出答案并且你也接受了一个问题之后。我的回答现在有什么意义?想象一个用户遇到了与您的“新”用户类似的问题,他们怎么会知道我的答案不是针对那个问题?我现在应该改变答案吗?如果其他人回答了您的“新”问题,那么您应该接受哪一个?您必须记住 *** 不仅适用于单个用户,还适用于社区:问题和答案应该对每个人都有帮助。创建一个新问题,我已经回滚了你的问题。 @musicmante 好吧,我的错,我认为行和列的解决方案可能非常相似,因为这两种方法具有相同的文档。我会做一个新的 @Esostack 它们相似的事实并不重要:您的原始问题和我的答案是指行移动,这需要不同的方法调用和更改 的过程内部数据的顺序;这会使我的答案无效,没有经验的用户可能不知道其中的区别,他们可能会被引导相信我的答案是有效的。如果您需要关于类似问题的说明,请使用 cmets,以便我们可以告诉您答案是否仍然正确,是否需要调整或(就像在这种情况下,因为出现了新问题)a 新问题是必填项。

以上是关于如何使用 beginMoveRows 在 QTableView (QAbstractTableModel) 中移动一行?的主要内容,如果未能解决你的问题,请参考以下文章

Qt5中关于beginMoveRows函数的坑

qtabwidget切换tab如何修改

如何从父级不是 QMainWindow 的 QWidget 访问 QMainWindow

如何在保持程序化行选择的同时防止用户点击行选择?

如何在Qt Creator中使两个小部件相互连接并相互调整大小[重复]

QTableWidget