1. Intro
I'm working in Python 3.7 on Windows 10 and use PyQt5 for the GUI. In my application, I got a QScrollArea()
with an array of buttons inside. When clicked, a button has to move outside the area. I use a QPropertyAnimation()
to show the movement.
2. Minimal, Reproducible Example
I've created a small application for testing. The application shows a small QScrollArea()
with a bunch of buttons inside. When you click on a button, it will move to the right:
Here is the code:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class MyButton(QPushButton):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setFixedWidth(300)
self.setFixedHeight(30)
self.clicked.connect(self.animate)
return
def animate(self):
self.anim = QPropertyAnimation(self, b'position')
self.anim.setDuration(3000)
self.anim.setStartValue(QPointF(self.pos().x(), self.pos().y()))
self.anim.setEndValue(QPointF(self.pos().x() + 200, self.pos().y() - 20))
self.anim.start()
return
def _set_pos_(self, pos):
self.move(pos.x(), pos.y())
return
position = pyqtProperty(QPointF, fset=_set_pos_)
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()
return
if __name__== '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Plastique'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())
3. The problem
When the button moves, it stays in the QScrollArea()
. I would need to get it on top of everything:
4. Solution (almost)
Thank you @magrif for pointing me in the right direction. Thanks to your suggestions, I got something working.
So I changed the animate()
function into this:
def animate(self):
self.anim = QPropertyAnimation(self, b'position')
self.anim.setDuration(3000)
startpoint = self.mapToGlobal(self.pos())
endpoint = self.mapToGlobal(QPoint(self.pos().x() + 200, self.pos().y() - 20))
self.setWindowFlags(Qt.Popup)
self.show()
self.anim.setStartValue(QPointF(startpoint.x(), startpoint.y()))
self.anim.setEndValue(QPointF(endpoint.x(), endpoint.y()))
self.anim.start()
QTimer.singleShot(1000, self.hide)
return
Note that I install a single-shot timer to hide()
the button after one second. That's because the Qt eventloop is blocked as long as this button behaves as a "popup" (because of self.setWindowFlags(Qt.Popup)
). Anyway, the one-shot timer works good enough for me.
Unfortunately I got one issue left. When I click on the first button Foo, it starts its animation (almost) from where it was sitting initially. If I click on one of the other buttons - like Baz - it suddenly jumps down about 100 pixels and starts its animation from there.
I think this has something to do with the startpoint = self.mapToGlobal(self.pos())
function and the fact that those buttons are sitting in a QScrollArea()
. But I don't know how to fix this.
5. Objective
My purpose is to build a rightmouse-click-menu like this:
When the user clicks on Grab and move, the button should disappear from the QScrollArea()
and move quickly towards the mouse. When it arrives at the mouse pointer, the button should fade out and the drag-and-drop operation can start.
Note: The following question related to this topic is this one:
Qt: How to perform a drag-and-drop without holding down the mouse button?