Trying to implement drag and drop for QAbstractItemModel

Rodrigo de Salvo Braz rodrigobraz at gmail.com
Tue Apr 20 07:26:30 BST 2021


 Wow, Maurizio, thank you very much, what an awesome and informative answer!

The example worked, but if you don't mind, I have some questions. Please
see them inline.

On Sun, Apr 18, 2021 at 8:35 PM Maurizio Berti <maurizio.berti at gmail.com>
wrote:

> An important thing to remember is that you can't always completely rely on
> IDEs for debugging: not only they will probably hide the actual exception
> depending on their implementations, but they normally don't show the full
> traceback. When in doubt, just run your program from terminal/prompt and
> check the output.
>

This was great to learn! PyCharm indeed does not show the errors and I had
been "flying blind" this whole time...

Then, the problem is that you didn't implement the most important function
> for drop events in a model: dropMimeData().
>

Ah, I thought that was required for external drops only.

Drag and drop always happens (or should happen) through QMimeData in Qt
> models. Qt views (and their models) use the
> "application/x-qabstractitemmodeldatalist" format to serialize data, which
> is created by calling QAbstractItemModel.mimeData (see
> https://doc.qt.io/qt-5/qabstractitemmodel.html#mimeData), and the
> structure is the following:
>

Now, where did you learn about this format specification? I did not see it
anywhere in the Qt documentation.

Here is a possible implementation:
>

Thanks a lot for writing this! It's very useful. I had to make a couple of
minor fixes, so I am posting the whole thing again in case someone will
reuse it at some point.

I do have a question, though. Why do we need the condition itemRow <
self.rowCount(QModelIndex())
and itemCol < self.columnCount(QModelIndex()) ? For an internal drop,
itemRow and itemCol should always satisfy it. And for external drops, they
could be larger than rowCount and colCount, but that should not matter as
long as their different from startRow and startCol are small enough:
(itemRow - startRow) < self.rowCount(QModelIndex()) and for itemCol
analogously.

Going a bit beyond this particular example, what happens if the method
mimeData has a list with multiple types? Method mimeData is supposed to
return a single QMimeData, so there will be a single encoding of the data,
and if so, for which of the multiple mime types?

Also, in case of multiple supported mime types, how can dropMimeData know
the format in which 'data' is encoded? It does not seem QMimeData provides
a method indicating its format.

def dropMimeData(self, data, action, row, column, parent):
    decoded = data.data('application/x-qabstractitemmodeldatalist')
    stream = QDataStream(decoded, QIODevice.ReadOnly)
    items = []
    startRow = startCol = 65536
    if parent.isValid():
        row = parent.row()
        column = parent.column()
    while not stream.atEnd():
        itemRow = stream.readInt()
        itemCol = stream.readInt()
        fieldCount = stream.readInt()
        display = None
        # displayValid = True
        displayValid = False # seems like you meant False here
        while fieldCount:
            role = stream.readInt()
            value = stream.readQVariant()
            if role == Qt.DisplayRole:
                displayValid = True
                display = value
            fieldCount -= 1
        if displayValid and itemRow < self.rowCount(QModelIndex()) and
itemCol < self.columnCount(QModelIndex()):
            # I had to add QModelIndex. It seems that is required in PyQt 5.9.2
            startRow = min(startRow, itemRow)
            startCol = min(startCol, itemCol)
            items.append((itemRow, itemCol, display))
    if not items:
        return False

    minRow = minCol = 65536
    maxRow = maxCol = 0
    for itemRow, itemCol, value in items:
        targetRow = row + (itemRow - startRow)
        targetCol = column + (itemCol - startCol)
        minRow = min(targetRow, minRow)
        maxRow = max(targetRow, maxRow)
        minCol = min(targetCol, minCol)
        maxCol = max(targetCol, maxCol)
        if targetRow < len(self.dataList) and targetCol <
len(self.dataList[targetRow]):  # minor fix
             self.dataList[targetRow][targetCol] = value
    self.dataChanged.emit(
        self.index(minRow, minCol),
        self.index(maxRow, maxCol),
    )
    return True

Thank you!

Rodrigo
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20210419/d6299e0f/attachment-0001.htm>


More information about the PyQt mailing list