How To Perform A Drag-and-drop Without Holding Down The Mouse Button?
Solution 1:
1. Solution
Before presenting a solution, I want to express my gratitude to Mr. @eyllanesc for helping me. Without his help, I wouldn't have a solution right now.
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys, functools
classMyButton(QPushButton):
'''
A special push button.
'''def__init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setFixedWidth(300)
self.setFixedHeight(30)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.showMenu)
self.dragStartPosition = 0
self.set_style(False)
returndefset_style(self, blink):
if blink:
background = "#d3d7cf"else:
background = "#2e3436"
self.setStyleSheet(f"""
QPushButton {{
/* white on red */
background-color:{background};
color:#ffffff;
border-color:#888a85;
border-style:solid;
border-width:1px;
border-radius: 6px;
font-family:Courier;
font-size:10pt;
padding:2px 2px 2px 2px;
}}
""")
self.update()
returndefshowMenu(self, pos):
'''
Show this popup menu when the user clicks with the right mouse button.
'''
menu = QMenu()
menuAction_01 = menu.addAction("action 01")
menuAction_02 = menu.addAction("action 02")
menuAction_03 = menu.addAction("action 03")
menuAction_04 = menu.addAction("action 04")
menuAction_grab = menu.addAction("grab")
action = menu.exec_(self.mapToGlobal(pos))
if action == menuAction_01:
print("clicked on action 01")
elif action == menuAction_02:
print("clicked on action 02")
elif action == menuAction_03:
print("clicked on action 03")
elif action == menuAction_04:
print("clicked on action 04")
elif action == menuAction_grab:
print("clicked on grab")
# Start animation -> button moves to mouse pointer
self.animate()
returndefanimate(self):
'''
The button removes itself from the QScrollArea() and flies to the mouse cursor.
For more details, see the anser of @eyllanesc at
https://stackoverflow.com/questions/56216698/how-display-a-qpropertyanimation-on-top-of-the-qscrollarea
'''defstart():
startpoint = self.window().mapFromGlobal(self.mapToGlobal(QPoint()))
endpoint = self.window().mapFromGlobal(QCursor.pos() - QPoint(int(self.width()/2), int(self.height()/2)))
self.setParent(self.window())
anim = QPropertyAnimation(
self,
b"pos",
self,
duration=500,
startValue=startpoint,
endValue=endpoint,
finished=blink,
)
anim.start()
self.show()
returndefblink():
# Flash the button to catch attention
self.setText("GRAB ME")
QTimer.singleShot(10, functools.partial(self.set_style, True))
QTimer.singleShot(100, functools.partial(self.set_style, False))
QTimer.singleShot(200, functools.partial(self.set_style, True))
QTimer.singleShot(300, functools.partial(self.set_style, False))
QTimer.singleShot(400, functools.partial(self.set_style, True))
QTimer.singleShot(500, functools.partial(self.set_style, False))
finish()
returndeffinish():
# After two seconds, hide the button# (even if user did not grab it)
QTimer.singleShot(2000, self.hide)
return
start()
returndefstart_drag(self):
'''
Start the drag operation.
'''# 1. Start of drag-and-drop operation# => button must disappear
self.hide()
# 2. Initiate drag-and-drop
drag = QDrag(self)
pixmap = QPixmap("my_pixmap.png")
pixmap = pixmap.scaledToWidth(100, Qt.SmoothTransformation)
drag.setPixmap(pixmap)
mimeData = QMimeData()
mimeData.setText("Foobar")
drag.setMimeData(mimeData)
dropAction = drag.exec(Qt.CopyAction | Qt.MoveAction)
returndefmousePressEvent(self, event):
'''
Left or Right mouseclick
'''defleftmouse():
print("left mouse click")
self.dragStartPosition = event.pos()
returndefrightmouse():
print("right mouse click")
returnif event.button() == Qt.LeftButton:
leftmouse()
returnif event.button() == Qt.RightButton:
rightmouse()
returnreturndefmouseMoveEvent(self, event):
'''
Mouse move event
'''
event.accept()
if event.buttons() == Qt.NoButton:
returnif self.dragStartPosition isNone:
returnif (event.pos() - self.dragStartPosition).manhattanLength() < QApplication.startDragDistance():
return
self.start_drag()
returnclassCustomMainWindow(QMainWindow):
def__init__(self):
super().__init__()
self.setGeometry(100, 100, 600, 300)
self.setWindowTitle("ANIMATION & DRAG AND DROP")
# OUTER FRAME# ============
self.frm = QFrame()
self.frm.setStyleSheet("""
QFrame {
background: #d3d7cf;
border: none;
}
""")
self.lyt = QHBoxLayout()
self.frm.setLayout(self.lyt)
self.setCentralWidget(self.frm)
# BUTTON FRAME# =============
self.btn_frm = QFrame()
self.btn_frm.setStyleSheet("""
QFrame {
background: #ffffff;
border: none;
}
""")
self.btn_frm.setFixedWidth(400)
self.btn_frm.setFixedHeight(200)
self.btn_lyt = QVBoxLayout()
self.btn_lyt.setAlignment(Qt.AlignTop)
self.btn_lyt.setSpacing(5)
self.btn_frm.setLayout(self.btn_lyt)
# SCROLL AREA# ============
self.scrollArea = QScrollArea()
self.scrollArea.setStyleSheet("""
QScrollArea {
border-style: solid;
border-width: 1px;
}
""")
self.scrollArea.setWidget(self.btn_frm)
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setFixedWidth(400)
self.scrollArea.setFixedHeight(150)
self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.lyt.addWidget(self.scrollArea)
# ADD BUTTONS TO BTN_LAYOUT# ==========================
self.btn_lyt.addWidget(MyButton("Foo"))
self.btn_lyt.addWidget(MyButton("Bar"))
self.btn_lyt.addWidget(MyButton("Baz"))
self.btn_lyt.addWidget(MyButton("Qux"))
self.show()
self.setAcceptDrops(True)
returndefdropEvent(self, event):
event.acceptProposedAction()
print("dropEvent at {0!s}".format(event))
returndefdragLeaveEvent(self, event):
event.accept()
returndefdragEnterEvent(self, event):
event.acceptProposedAction()
returnif __name__== '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Plastique'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())
This is what I changed:
I improved the animation. Not the top left corner but the middle of the button flies to your mouse pointer when you click "grab" in the rightmouse menu. This improvement makes it much easier to grab the button once the animation has finished.
At the end of the animation, the button flashes for a short moment to catch the user's attention. The text on the button changes into "GRAB ME". The button's
self.hide()
function is NOT called for two seconds. So the user has two seconds time to initiate a drag-and-drop operation.Initiating the drag-and-drop operation happens in the usual way: hold down the leftmouse button and move the mouse pointer.
If the user doesn't do anything for two seconds, the button will just disappear. Otherwise, the button would just sit there indefinitely.
2. Results
It works like a charm. Just copy past the code in a .py
file and run it with Python 3.x (I got Python 3.7) and PyQt5:
3. Conclusion
I know that this solution is not exactly what I aimed at from the beginning: performing a drag-and-drop without holding down the mouse button. Nevertheless, I think the new approach is better because it's closer to the general intuition what a drag-and-drop actually is.
Post a Comment for "How To Perform A Drag-and-drop Without Holding Down The Mouse Button?"