我的目的是建立一个像这样的鼠标右键单击菜单:
当用户点击抓取并移动时,该按钮应从QScrollArea()
中消失,并迅速移向鼠标。当它到达鼠标指针时,该按钮应淡出,并且拖放操作可以开始。
我有一些工作要做,但还不是很完美。请复制粘贴以下代码,然后使用 Python 3.x (我使用Python 3.7)和 PyQt5 来运行它。
注意:要使
pixmap = QPixmap("my_pixmap.png")
行正常工作,请使其引用计算机上的现有png图像。
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class MyButton(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)
return
def showMenu(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")
# 1. Start animation
# -> button moves to mouse pointer
self.animate()
# 2. After animation finishes (about 1 sec)
# -> start drag operation
QTimer.singleShot(1000, self.start_drag)
return
def animate(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
'''
startpoint = self.window().mapFromGlobal(self.mapToGlobal(QPoint()))
endpoint = self.window().mapFromGlobal(QCursor.pos())
self.setParent(self.window())
anim = QPropertyAnimation(
self,
b"pos",
self,
duration=1000,
startValue=startpoint,
endValue=endpoint,
finished=self.hide,
)
anim.start()
self.show()
return
def start_drag(self):
'''
Start the drag operation.
'''
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)
return
class CustomMainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(100, 100, 600, 300)
self.setWindowTitle("ANIMATION TEST")
# 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)
return
def dropEvent(self, event):
event.acceptProposedAction()
print("dropEvent at {0!s}".format(event))
return
def dragLeaveEvent(self, event):
event.accept()
return
def dragEnterEvent(self, event):
event.acceptProposedAction()
return
if __name__== '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Plastique'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())
运行脚本,您将在QScrollArea()
中看到一个带有几个按钮的小窗口:
步骤1:用鼠标右键单击其中一个按钮。您应该会看到一个弹出菜单。点击“抓取”。
步骤2:该按钮移至鼠标指针。不要移动鼠标指针。
步骤3:一旦鼠标指针悬停在按钮上方(不要移动鼠标,等待按钮到达),请单击并按住鼠标按钮。
步骤4:现在移动鼠标(按住鼠标按钮的同时)。您应该在拖放操作中,将像素图锁定在鼠标上!
好的,可以,但是有一些缺点。
动画结束时,飞行按钮位于鼠标指针下方。但是,如果将鼠标指针稍微移动一点,该按钮就会消失,并且会错过拖放操作。
换句话说,我现在得到的不是很可靠。用户可以轻松地错过拖放操作。
注意:显然,我在此描述的问题仅出现在Windows(而不是Linux)上。但是我必须使这个东西在Windows上可以工作...
我相信以下方法会更好,并且对用户仍然直观:
一旦按钮到达鼠标指针下方(动画的结尾),按钮就会消失。 拖放操作将自动启动,而无需单击并按住鼠标按钮。在移动鼠标指针时,拖动将继续,直到单击某处。鼠标按下的是dropEvent()
。
您知道如何实现吗?也许您在想另一种方法?
我的问题实际上是这个问题的续集:
How display a QPropertyAnimation() on top of the QScrollArea()?
谢谢@eyllanesc解决了一个^ _ ^
答案 0 :(得分:1)
在提出解决方案之前,我要感谢@eyllanesc先生为我提供的帮助。没有他的帮助,我现在将没有解决方案。
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys, functools
class MyButton(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)
return
def set_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()
return
def showMenu(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()
return
def animate(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
'''
def start():
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()
return
def blink():
# 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()
return
def finish():
# After two seconds, hide the button
# (even if user did not grab it)
QTimer.singleShot(2000, self.hide)
return
start()
return
def start_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)
return
def mousePressEvent(self, event):
'''
Left or Right mouseclick
'''
def leftmouse():
print("left mouse click")
self.dragStartPosition = event.pos()
return
def rightmouse():
print("right mouse click")
return
if event.button() == Qt.LeftButton:
leftmouse()
return
if event.button() == Qt.RightButton:
rightmouse()
return
return
def mouseMoveEvent(self, event):
'''
Mouse move event
'''
event.accept()
if event.buttons() == Qt.NoButton:
return
if self.dragStartPosition is None:
return
if (event.pos() - self.dragStartPosition).manhattanLength() < QApplication.startDragDistance():
return
self.start_drag()
return
class CustomMainWindow(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)
return
def dropEvent(self, event):
event.acceptProposedAction()
print("dropEvent at {0!s}".format(event))
return
def dragLeaveEvent(self, event):
event.accept()
return
def dragEnterEvent(self, event):
event.acceptProposedAction()
return
if __name__== '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Plastique'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())
这就是我所做的更改:
我改进了动画。当您单击鼠标右键菜单中的“抓取”时,按钮的中心不是左上角而是鼠标中点。这项改进使动画制作完成后更容易抓住按钮。
在动画结束时,按钮闪烁一小段时间以引起用户的注意。按钮上的文本变为“ GRAB ME”。两秒钟未调用按钮的self.hide()
函数。因此,用户有两秒钟的时间来启动拖放操作。
开始拖放操作的方式通常是:按住鼠标左键并移动鼠标指针。
如果用户两秒钟不执行任何操作,则该按钮将消失。否则,按钮将无限期地坐在那里。
它就像一种魅力。只需将代码复制到.py
文件中,然后使用 Python 3.x (我得到了 Python 3.7 )和 PyQt5 :
我知道此解决方案并非一开始就针对我的目标:无需按住鼠标键即可执行拖放操作。不过,我认为这种新方法更好,因为它实际上更接近拖放的实际感觉。