slomo
slomo

Reputation: 117

PyQt6 QTreeView Drag & Drop Indicator Not Showing After Overriding Drag Events

I'm trying to implement drag-and-drop functionality in a QTreeView widget in PyQt6. I have enabled drag-and-drop support using setDragEnabled(True), setAcceptDrops(True), and setDropIndicatorShown(True) to show the drop indicator when dragging items.

However, after overriding the built-in drag and drop event functions (dragEnterEvent, dragMoveEvent, and dropEvent), the drop indicator (the line between rows where the dragged item can be dropped) no longer appears.

How can I restore or properly display the drop indicator between items after overriding the drag-and-drop event functions in PyQt6's QTreeView?

Here is the code I have so far:

from PyQt6 import QtCore, QtGui, QtWidgets

class ObjectTree(QtWidgets.QTreeView):
    def __init__(self):
        super().__init__()

        self.model = QtGui.QStandardItemModel()
        self.setModel(self.model)

        # Enable drag and drop
        self.setDragEnabled(True)
        self.setAcceptDrops(True)
        self.setDropIndicatorShown(True)  # Show drop indicator

        self._populate_tree()

    def _populate_tree(self):
        root_item = QtGui.QStandardItem('Root')
        self.model.appendRow(root_item)
        for i in range(10):
            child_item = QtGui.QStandardItem(f'Child {i + 1}')
            root_item.appendRow(child_item)

    def dragEnterEvent(self, event):
        if event.mimeData().hasFormat('application/x-item'):
            event.acceptProposedAction()
        else:
            event.ignore()

    def dragMoveEvent(self, event):
        if event.mimeData().hasFormat('application/x-item'):
            event.acceptProposedAction()
        else:
            event.ignore()

    def dropEvent(self, event):
        if event.mimeData().hasFormat('application/x-item'):
            data = event.mimeData().data('application/x-item')
            stream = QtCore.QDataStream(data, QtCore.QIODevice.OpenModeFlag.ReadOnly)
            item_name = stream.readQString()
            print(f"Dropped item: {item_name}")
            event.acceptProposedAction()

    def startDrag(self, event):
        selected_indexes = self.selectedIndexes()
        if selected_indexes:
            selected_item = self.model.itemFromIndex(selected_indexes[0])
            mime_data = QtCore.QMimeData()
            data = QtCore.QByteArray()
            stream = QtCore.QDataStream(data, QtCore.QDataStream.OpenModeFlag.WriteOnly)
            stream.writeQString(selected_item.text())
            mime_data.setData('application/x-item', data)

            drag = QtGui.QDrag(self)
            drag.setMimeData(mime_data)
            drag.exec(event)

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.object_tree = ObjectTree()
        self.setCentralWidget(self.object_tree)

        self.setWindowTitle("PyQt6 Drag and Drop Example")
        self.setGeometry(100, 100, 600, 400)

if __name__ == "__main__":
    app = QtWidgets.QApplication([])  
    window = MainWindow()  
    window.show()  
    app.exec()  

Upvotes: 0

Views: 46

Answers (1)

slomo
slomo

Reputation: 117

Based on the suggestion by @musicamante, I've updated the code to use model subclassing for drag and drop functionality. This approach correctly displays the drop indicator and provides a cleaner implementation.

Here's the corrected code:

from PyQt6 import QtCore, QtGui, QtWidgets

class ObjectTreeModel(QtGui.QStandardItemModel):
    def __init__(self):
        super().__init__()

    def mimeTypes(self):
        return ['application/x-item']

    def mimeData(self, indexes):
        if not indexes:
            return None
        item = self.itemFromIndex(indexes[0])
        mime_data = QtCore.QMimeData()
        data = QtCore.QByteArray()
        stream = QtCore.QDataStream(data, QtCore.QDataStream.OpenModeFlag.WriteOnly)
        stream.writeQString(item.text())
        mime_data.setData('application/x-item', data)
        return mime_data

    def dropMimeData(self, data, action, row, column, parent):
        if not data.hasFormat('application/x-item'):
            return False

        stream = QtCore.QDataStream(data.data('application/x-item'), QtCore.QIODevice.OpenModeFlag.ReadOnly)
        item_name = stream.readQString()
        print(f"Dropped item: {item_name}")

        if parent and parent.isValid():
            parent_item = self.itemFromIndex(parent)
            new_item = QtGui.QStandardItem(item_name)
            parent_item.insertRow(row,new_item)
        else:
            new_item = QtGui.QStandardItem(item_name)
            self.insertRow(row, new_item)
        return True

    def flags(self, index):
         default_flags = super().flags(index)
         if index.isValid():
            return default_flags | QtCore.Qt.ItemFlag.ItemIsDragEnabled | QtCore.Qt.ItemFlag.ItemIsDropEnabled
         else:
             return default_flags | QtCore.Qt.ItemFlag.ItemIsDropEnabled


class ObjectTree(QtWidgets.QTreeView):
    def __init__(self):
        super().__init__()

        self.model = ObjectTreeModel()
        self.setModel(self.model)

        # Enable drag and drop
        self.setDragEnabled(True)
        self.setAcceptDrops(True)
        self.setDropIndicatorShown(True)

        self._populate_tree()

    def _populate_tree(self):
        root_item = QtGui.QStandardItem('Root')
        self.model.appendRow(root_item)
        for i in range(10):
            child_item = QtGui.QStandardItem(f'Child {i + 1}')
            root_item.appendRow(child_item)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.object_tree = ObjectTree()
        self.setCentralWidget(self.object_tree)

        self.setWindowTitle("PyQt6 Drag and Drop Example")
        self.setGeometry(100, 100, 600, 400)

if __name__ == "__main__":
    app = QtWidgets.QApplication([])
    window = MainWindow()
    window.show()
    app.exec()

This updated code moves the drag and drop logic to a custom ObjectTreeModel which inherits from QtGui.QStandardItemModel. This approach allows the QTreeView to properly handle the display of the drop indicator.

Key changes:

  • The ObjectTreeModel now handles the mimeTypes(), mimeData(), and dropMimeData() methods.

  • The flags() method is updated to enable dragging and dropping on the items.

  • The ObjectTree class now uses an instance of ObjectTreeModel.

  • Removed the overridden event handlers from the ObjectTree class

By using this approach, we can leverage the default behavior of QTreeView and correctly display the drop indicator.

Upvotes: 1

Related Questions