'Implementing copy and move when dropping an Item in `QTreeView`
I revised the whole question because the behavior I want is hard to implement and actually use.
I'm trying to imitate the behavior in the File Explorer where when I press Shift while dragging, the file will be moved instead of copied.
This is the behavior I'm trying to imitate:

The behavior: is I'm using my LeftClick for selecting, and dragging.

About The behavior itself:
I overridden the mousePressEvent and mouseMoveEvent to start the drag. When the drag is created, it uses QTimer to detect if I pressed the Control and Shift modifier. Once a modifier is detected it sets the default drop action using setDefaultDropAction. (I think I should use setDropAction but It's only available in the dragMoveEvent and I'm doing it inside the QDrag Class)
The Issues: Part of the behavior is working now but there is still some issues.
- Even I press
Shift, the DropIndicator is not changing from+to-> - Related to the issue above, The dropAction is only
copyActioninstead ofmoveActioneven I'm pressing theShiftkey.
My Question: What causes these issues? My gut tells me that I should've used setDropAction instead of setDefaultDropAction but again it's only available in the dragMoveEvent
My Testing Code:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class ModifiedQDrag(QDrag):
def __init__(self, source):
super().__init__(source)
self.timer = QTimer(self)
self.timer.timeout.connect(self.process_event)
self.timer.setInterval(100)
self.timer.start()
def process_event(self):
if qApp.keyboardModifiers() & Qt.ControlModifier:
self.source().setDefaultDropAction(Qt.CopyAction)
elif qApp.keyboardModifiers() & Qt.ShiftModifier:
print("shift pressed")
self.source().setDefaultDropAction(Qt.MoveAction)
class Tree(QTreeView):
def __init__(self):
super().__init__()
self.setDragDropMode(QAbstractItemView.DragDrop)
self.setDropIndicatorShown(True)
self.viewport().setAcceptDrops(True)
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
# -- mouse dragging -- #
def mousePressEvent(self, event):
if event.button() == Qt.RightButton:
self.dragStartPosition = event.pos()
return super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if event.buttons() != Qt.RightButton:
return
if ((event.pos() - self.dragStartPosition).manhattanLength() < QApplication.startDragDistance()):
return
drag = ModifiedQDrag(self)
mimeData = QMimeData()
mimeData = self.model().mimeData([self.indexAt(event.pos())])
drag.setMimeData(mimeData)
dragAction = drag.exec(Qt.MoveAction | Qt.CopyAction, Qt.CopyAction)
return super().mouseMoveEvent(event)
def dragMoveEvent(self, event):
m = event.mimeData()
if m.hasUrls():
event.accept()
return
event.ignore()
def dropEvent(self, event):
print("[drop event] - dropped")
class FileSystemView(QWidget):
def __init__(self):
super().__init__()
# -- left side -- #
left_side_dir = r"<Dir>"
self.model = QFileSystemModel()
self.model.setRootPath(left_side_dir)
self.tree = Tree()
self.tree.setModel(self.model)
self.tree.setRootIndex(self.model.index(left_side_dir))
# -- right side -- #
right_side_dir = r"<Dir>"
self.model2 = QFileSystemModel()
self.model2.setRootPath(right_side_dir)
self.tree2 = Tree()
self.tree2.setModel(self.model2)
self.tree2.setRootIndex(self.model2.index(right_side_dir))
# -- layout -- #
self.tree_layout = QHBoxLayout()
self.tree_layout.addWidget(self.tree)
self.tree_layout.addWidget(self.tree2)
self.setLayout(self.tree_layout)
app = QApplication(sys.argv)
demo = FileSystemView()
demo.show()
sys.exit(app.exec_())
Solution 1:[1]
Qt can only react to mouse movements in order to trigger changes in the drop action: as the name suggests, dragMoveEvent() can only be called by a mouse move.
Considering that, a possible solution is to manually force the mouse movement whenever the keyboard modifiers change. In this way you don't even need to create a QDrag subclass and you can keep the default behavior.
Be aware that to properly get modifiers, you should not use keyboardModifiers(), but queryKeyboardModifiers(), as the first is only reliable when keyboard events are directly handled and might not be updated with the actual current state of the keyboard.
class Tree(QTreeView):
# ...
def checkDrag(self):
modifiers = qApp.queryKeyboardModifiers()
if self.modifiers != modifiers:
self.modifiers = modifiers
pos = QCursor.pos()
# slightly move the mouse to trigger dragMoveEvent
QCursor.setPos(pos + QPoint(1, 1))
# restore the previous position
QCursor.setPos(pos)
def mouseMoveEvent(self, event):
if event.buttons() != Qt.RightButton:
return
if ((event.pos() - self.dragStartPosition).manhattanLength() < QApplication.startDragDistance()):
return
self.modifiers = qApp.queryKeyboardModifiers()
# a local timer, it will be deleted when the function returns
dragTimer = QTimer(interval=100, timeout=self.checkDrag)
dragTimer.start()
self.startDrag(Qt.MoveAction|Qt.CopyAction)
def dragMoveEvent(self, event):
if not event.mimeData().hasUrls():
event.ignore()
return
if qApp.queryKeyboardModifiers() & Qt.ShiftModifier:
event.setDropAction(Qt.MoveAction)
else:
event.setDropAction(Qt.CopyAction)
event.accept()
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | musicamante |
