Search code examples
pythonpyqtqtableviewqabstracttablemodel

Columns not properly moving in QTableView (QAbstractTableModel) using beginMoveColumns?


I am trying to use beginMoveColumns to move a single column over in a QTableView, but it doesn't work properly in my example below. The cell selections get shuffled and column widths don't move. Moving rows using the same logic seems to work correctly. What am I doing wrong?

Video: https://www.screencast.com/t/5UJ0iByZCEE

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:
            return "Header"

    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])

    def move_column(self, ix, new_ix):
        parent = QtCore.QModelIndex()
        if new_ix > ix:
            target = new_ix + 1
        else:
            target = new_ix
        self.beginMoveColumns(parent, ix, ix, parent, target)

        # Shift column in each row
        for row in self.data:
            row.insert(new_ix, row.pop(ix))

        self.endMoveColumns()

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

        # Shift row
        self.data.insert(new_ix, self.data.pop(ix))

        self.endMoveRows()


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

    data = []
    for i in range(12):
        row = []
        data.append(row)
        for j in range(6):
            row.append(f"R{i}_C{j}")

    model = MyTableModel(data)
    view = QtWidgets.QTableView()
    view.setModel(model)

    view.setColumnWidth(0, 50)
    view.setColumnWidth(1, 100)

    view.setRowHeight(0, 50)
    view.setRowHeight(1, 100)

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

    layout.addWidget(view)

    button = QtWidgets.QPushButton("Move 1st column right by 1")
    button.clicked.connect(lambda: model.move_column(0, 1))
    layout.addWidget(button)

    button = QtWidgets.QPushButton("Move 1st column right by 2")
    button.clicked.connect(lambda: model.move_column(0, 2))
    layout.addWidget(button)

    button = QtWidgets.QPushButton("Move 1st row down by 1")
    button.clicked.connect(lambda: model.move_row(0, 1))
    layout.addWidget(button)

    button = QtWidgets.QPushButton("Move 1st row down by 2")
    button.clicked.connect(lambda: model.move_row(0, 2))
    layout.addWidget(button)

    container.resize(800, 800)
    container.show()


    sys.exit(app.exec_())

Solution

  • Turns out it was a bug, I made a bug report here: https://bugreports.qt.io/browse/QTBUG-94503

    As a workaround I just clear cell selection on column move, and use this snippet to move column widths

    model = view.model()
    column_widths = [view.columnWidth(ix) for ix in range(model.columnCount())]
    column_widths.insert(new_ix, column_widths.pop(ix))
    
    # Set width of destination column to the width of the source column
    for j in range(len(column_widths)):
        view.setColumnWidth(j, column_widths[j])