如何在QScrollArea()顶部显示QPropertyAnimation()?

时间:2019-05-20 08:09:17

标签: python python-3.x pyqt pyqt5

1。简介

我正在Windows 10上的 Python 3.7 中工作,并将 PyQt5 用于GUI。在我的应用程序中,我得到了一个QScrollArea(),其中包含一系列按钮。单击时,按钮必须移到该区域之外。我使用QPropertyAnimation()来显示运动。


2。最小的,可重现的示例

我已经创建了一个用于测试的小型应用程序。该应用程序显示一个小的QScrollArea(),其中带有一堆按钮。当您单击按钮时,它将移至右侧:

enter image description here

代码如下:

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。问题

按钮移动时,它停留在QScrollArea()中。我需要把它放在一切之上:

enter image description here


4。解决方案(几乎)

感谢@magrif向我指出正确的方向。感谢您的建议,我的工作有所进展。

因此,我将animate()函数更改为:

    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

请注意,一秒钟后,我在hide()按钮上安装了单发计时器。这是因为只要此按钮的行为像“弹出”(由于self.setWindowFlags(Qt.Popup)),Qt事件循环就被阻止。无论如何,单拍定时器对我来说足够好了。

很遗憾,我还剩下一期。当我单击第一个按钮 Foo 时,它几乎从最初的位置开始播放动画。如果我点击其他按钮之一(例如 Baz ),它会突然跳下约100像素并从此处开始动画。

我认为这与startpoint = self.mapToGlobal(self.pos())函数以及这些按钮位于QScrollArea()中有关。但是我不知道该如何解决。


5。目标

我的目的是建立一个像这样的鼠标右键单击菜单:

enter image description here

当用户点击抓取并移动时,该按钮应从QScrollArea()中消失,并迅速移向鼠标。当它到达鼠标指针时,该按钮应淡出,并且拖放操作可以开始。

注意:与该主题相关的以下问题是这个问题:
Qt: How to perform a drag-and-drop without holding down the mouse button?

2 个答案:

答案 0 :(得分:2)

小部件的位置是相对于其父级的,所以您不应使用startpoint = self.mapToGlobal(self.pos()),而应使用startpoint = self.mapToGlobal(QPoint()),因为对于小部件,topLeft的位置是QPoint(0, 0)。 / p>

因此,如果您想使用@magrif解决方案,则应将其更改为:

def animate(self):
    startpoint = self.mapToGlobal(QPoint())
    self.setWindowFlags(Qt.Popup)
    self.show()
    anim = QPropertyAnimation(
        self,
        b"pos",
        self,
        duration=3000,
        startValue=startpoint,
        endValue=startpoint + QPoint(200, -20),
        finished=self.deleteLater,
    )
    anim.start()

但是缺点是,动画运行时您无法与窗口进行交互。

另一种解决方案是将QFrame的父级更改为窗口本身:

def animate(self):
    startpoint = self.window().mapFromGlobal(self.mapToGlobal(QPoint()))
    self.setParent(self.window())
    anim = QPropertyAnimation(
        self,
        b"pos",
        self,
        duration=3000,
        startValue=startpoint,
        endValue=startpoint + QPoint(200, -20),
        finished=self.deleteLater,
    )
    anim.start()
    self.show()

注意:由于qproperty pos已经存在,因此不必创建qproperty位置。

答案 1 :(得分:1)

  1. 使用mapToGlobal()计算按钮的全局坐标。

  2. 使用Qt.PopupsetWindowFlags()

  3. 设置标志show()
  4. 初始开始值和结束值与(1)中的全局坐标相关,并开始动画

ps在C ++上工作:)