Reputation: 117
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
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