0

I'm working on an application that uses a QTableView and have been trying to get it to respond correctly to dragging and dropping rows in order to rearrange them. After a lot of difficulty, I found this comment on a post that was describing the exact problems that I was having with rows "disappearing" when dropped. The comment suggested a more intuitive method would be available in Qt 6.9, and I recalled that PyQt 6.9 was recently released.

I updated my version of the library and tested out an implementation in a minimal example (below). I saw that it worked great for the two requirements I needed:

  1. A user can only drop rows between existing rows, before the first row, or after the last row. There's no way to overwrite a row by dropping a row on top of it.

  2. The row should be inserted at column 0 all the time, to avoid the dropped row being offset and ending up adding columns to the model/view.

However, when I went to implement in my full application, it stopped working as intended. I narrowed the issue down to the fact that my application hides column 0. When this happens, there's two impacts:

  1. The dropIndicator in the table stops appearing correctly between rows.

  2. Dropping a row between other rows causes the data in column 0 for the moved row to be lost, and all remaining columns' data decrement their positions in the row.

I think I can work around this; I was hiding column 0 because at the time I wrote the code I didn't want the data in the column to be visible in the table. I've since learned that I can place additional data in an index under another role, so I think I can do that instead and not hide any columns.

However, I'd love to know more about why hiding the column breaks the intended functionality if anyone knows why. Uncommenting line 21 in the code should illustrate what I am seeing.

import sys

from PyQt6.QtCore import Qt
from PyQt6.QtGui import QStandardItemModel, QStandardItem
from PyQt6.QtWidgets import (QApplication, QMainWindow, QTableView, QAbstractItemView, QWidget, QVBoxLayout, QPushButton)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)
        self.central_widget.setLayout(QVBoxLayout())
        self.table = QTableView()
        self.central_widget.layout().addWidget(self.table)
        self._config_table()
        self.model = self._make_model()
        self.table.setModel(self.model)
        self.table.resizeRowsToContents()
        self.table.resizeColumnsToContents()
        #self.table.hideColumn(0)
        self.button = QPushButton("Check")
        self.central_widget.layout().addWidget(self.button)
        self.button.clicked.connect(self._log_model_contents)
        self.show()

    def _config_table(self):
        self.table.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
        self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
        self.table.setDragEnabled(True)
        self.table.setAcceptDrops(True)
        self.table.setDropIndicatorShown(True)
        self.table.setDragDropOverwriteMode(False)

    def _make_model(self):
        data = [
            [1, 2, 3, 4, 5],
            ["a", "b", "c", "d", "e"],
            [5.2, 5.3, 5.4, 5.5, 5.6],
            [0, 0, None, 0, 9999]
        ]
        model = MyModel(0, 5)
        for r in data:
            items_row = list(map(lambda x: QStandardItem(str(x) if x is not None else None), r))
            for item in items_row:
                item.setFlags(Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsDragEnabled | 
                              Qt.ItemFlag.ItemIsEditable)
            model.appendRow(items_row)
        return model
    
    def _log_model_contents(self):
        for r in range(0, self.model.rowCount()):
            row_data = []
            for c in range(0, self.model.columnCount()):
                row_data.append(self.model.index(r, c).data(Qt.ItemDataRole.DisplayRole))
            print(r, row_data)


class MyModel(QStandardItemModel):

    def dropMimeData(self, data, action, row, column, parent):
        return super().dropMimeData(data, action, row, 0, parent)


app = QApplication([])
my_window = MainWindow()
sys.exit(app.exec())
6
  • 1
    Drag-drop operates on selected items. But hidden items cannot be selected, so they cannot form part of a drag/drop operation. In fact, examination of the source code shows that hidden items are always explicitly excluded from both the selected indexes and the draggable indexes. Given this, the behaviour you describe seems unsurprising. If you hide columns, expect abnormal behaviour. Commented Apr 18 at 11:15
  • @mikstravaganza An unrelated suggestion: list comprehensions are nice and fun, but should always be used with when reasonable. Creating a list with list(map(...)) that is then still iterated makes little sense: you'll only get a very long line that is poorly readable, complicates debugging, and gives practically no real benefit. You could change it into: for row in data: rowItems = [] for x in row: item = QStandardItem() if x is not None: item.setText(str(x)) item.setFlags(...) rowItems.append(item) and finally model.appendRow(rowItems) at the end of the first for. Commented Apr 20 at 2:24
  • Thank you @ekhumoro, I appreciate your insight. Given that, is there really a good use case for hiding columns in a table, then? Seems like it's more destructive than helpful. Commented Apr 20 at 16:07
  • Good point, and thank you @musicamante! I'm fairly certain I wrote the list comprehension before I knew exactly what I wanted to do with it, but well noted on the legibility concern :) Commented Apr 20 at 16:08
  • 1
    @mikstravaganza I suppose hiding columns is a lot less problematic for read-only views. However, I wouldn't claim that hiding columns is inherently "bad" - it's just that, given the current implementation, it may sometimes have undesirable consequences. If Qt had chosen a different design, you can bet there would be people complaining that it made no sense to include hidden columns in drag-drop operations. The default behaviour has to be one way or another. When it doesn't go your way, you must either reimplement the relevant APIs yourself, or redesign your own application accordingly. Commented Apr 20 at 19:01

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.