2025, Dec 26 21:00

Qt Item Views Drag and Drop: flags vs setDragDropMode, InternalMove semantics, and practical examples

Learn how Qt item views handle drag and drop: flags vs setDragDropMode, what InternalMove changes, and when to use dragEnabled and acceptDrops. Demos inside.

Drag and drop in Qt’s item views often starts with a simple question: should you flip the individual flags like dragEnabled and acceptDrops, or call setDragDropMode and be done with it? The two paths look similar from the outside, but they are not identical in what they record internally and how they report state back to you.

Minimal example that surfaces the question

The snippet below creates two list widgets. The first one enables drag and drop via individual switches. The second one uses the mode API. The printed state helps compare what the view thinks about its DnD configuration.

from qtpy.QtWidgets import QApplication, QWidget, QListWidget, QHBoxLayout

class DemoPane(QWidget):

    labels = ["Sycamore", "Chestnut", "Walnut", "Mahogany"]

    def __init__(self):
        super().__init__()

        list_a = QListWidget()
        list_a.addItems(self.labels)
        self.show_state("Initial", list_a)

        list_b = QListWidget()
        list_b.addItems(self.labels)

        # Configure via individual properties
        list_a.setDragEnabled(True)
        list_a.viewport().setAcceptDrops(True)
        list_a.setDropIndicatorShown(True)
        self.show_state("Configured via flags", list_a)

        # Configure via mode API
        mode = list_b.DragDrop
        list_b.setDragDropMode(mode)
        self.show_state(f"Configured via mode ({mode})", list_b)

        layout = QHBoxLayout(self)
        layout.addWidget(list_a)
        layout.addWidget(list_b)

    def show_state(self, title, view):
        print()
        print(title)
        print("drag:", view.dragEnabled())
        print("drop:", view.viewport().acceptDrops())
        print("mode:", view.dragDropMode())


def run():
    app = QApplication([])
    w = DemoPane()
    w.show()
    app.exec()

run()

What actually happens under the hood

The mode setter stores the selected mode internally and also flips the same switches you would set by hand. In other words, setDragDropMode adjusts dragEnabled and acceptDrops according to the chosen option. The getter, in turn, reconstructs a logical mode from the current flags and, when both dragging and dropping are active, uses the internally saved value to distinguish between DragDrop and InternalMove.

This explains the observation that DragDrop and InternalMove can share the same on/off state for drag/drop but still behave differently: the stored mode is what makes InternalMove special. By default the internal value starts as NoDragDrop, so if you only toggle the flags to true and never set an explicit mode, asking for dragDropMode later will report DragDrop. The accessor methods are not virtual, which means the behavior described is consistent across item-view subclasses.

So are the two approaches equivalent?

They overlap but are not strictly the same. Setting dragEnabled and acceptDrops directly replicates the on/off state that setDragDropMode would set for DragOnly, DropOnly, or DragDrop. The only extra information carried by setDragDropMode is the InternalMove distinction when both drag and drop are enabled.

Practical resolution and when to use which

If you don’t need InternalMove semantics, you can rely on the individual flags and skip the mode property entirely; behavior will match what the mode API produces. If you do need the InternalMove behavior, use setDragDropMode explicitly to record that intent. The mode is what differentiates the “move inside the same view” scenario from the generic “drag and drop” configuration that otherwise looks identical at the level of simple flags.

A focused fix when you need InternalMove

The following variant mirrors the previous setup, but the second widget explicitly selects InternalMove. That is the one case where setDragDropMode carries information you cannot express by toggling only the flags.

from qtpy.QtWidgets import QApplication, QWidget, QListWidget, QHBoxLayout

class MoveDemo(QWidget):

    items = ["Sycamore", "Chestnut", "Walnut", "Mahogany"]

    def __init__(self):
        super().__init__()

        left = QListWidget()
        left.addItems(self.items)
        left.setDragEnabled(True)
        left.viewport().setAcceptDrops(True)
        left.setDropIndicatorShown(True)
        self.report("Flags only", left)

        right = QListWidget()
        right.addItems(self.items)
        right.setDragDropMode(right.InternalMove)
        self.report("InternalMove mode", right)

        layout = QHBoxLayout(self)
        layout.addWidget(left)
        layout.addWidget(right)

    def report(self, title, view):
        print()
        print(title)
        print("drag:", view.dragEnabled())
        print("drop:", view.viewport().acceptDrops())
        print("mode:", view.dragDropMode())


def launch():
    app = QApplication([])
    w = MoveDemo()
    w.show()
    app.exec()

launch()

Why this distinction matters

Without understanding how the mode and the flags relate, it’s easy to misread the state of a view or to get identical-looking configurations that act differently at runtime. Knowing that setDragDropMode both toggles the flags and preserves a specific mode value clarifies why InternalMove stands apart from plain DragDrop, even when both show the same drag and drop capabilities.

Takeaways

Use the individual flags when you only need to enable or disable dragging and dropping. Reach for setDragDropMode when you need to encode a concrete behavior choice, in particular InternalMove. If InternalMove is not part of the requirements, the two approaches are interchangeable from a behavior standpoint, and you can keep your configuration minimal.