Как отобразить QPropertyAnimation () поверх QScrollArea ()? - PullRequest
3 голосов
/ 20 мая 2019

1.Intro

Я работаю в Python 3.7 на Windows 10 и использую PyQt5 для графического интерфейса.В моем приложении я получил 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()Кнопка через одну секунду.Это потому, что цикл событий Qt блокируется, пока эта кнопка ведет себя как «всплывающее окно» (из-за self.setWindowFlags(Qt.Popup)).В любом случае, таймер однократного срабатывания достаточно хорош для меня.

К сожалению, у меня осталась одна проблема.Когда я нажимаю на первую кнопку Foo , она начинает свою анимацию (почти) с того места, где она находилась изначально.Если я нажимаю на одну из других кнопок - например, Baz - он внезапно спрыгивает примерно на 100 пикселей и начинает отсюда анимацию.

Я думаю, что это как-то связано с startpoint = self.mapToGlobal(self.pos()) функция и тот факт, что эти кнопки находятся в QScrollArea().Но я не знаю, как это исправить.


5.Цель

Моя цель - создать меню щелчка правой кнопкой мыши, например:

enter image description here

Когда пользователь нажимает Хватайте и двигайте , кнопка должна исчезнуть с QScrollArea() и быстро двигаться к мышке.Когда он достигнет указателя мыши, кнопка должна исчезнуть, и операция drag-and-drop может начаться.

Примечание: Следующий вопросэта тема связана с:
Qt: Как выполнить перетаскивание без удерживания кнопки мыши?

Ответы [ 2 ]

2 голосов
/ 22 мая 2019

Позиция виджета относительно его родителя, поэтому вы не должны использовать startpoint = self.mapToGlobal(self.pos()), но startpoint = self.mapToGlobal(QPoint()), поскольку для виджета позиция topLeft равна QPoint(0, 0).

.если вы хотите использовать решение @ 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, поскольку qproperty pos уже существуетсуществует.

1 голос
/ 20 мая 2019
  1. Рассчитать глобальные координаты для кнопки, используя mapToGlobal().

  2. Установить флаг Qt.Popup, используя setWindowFlags() и show()

  3. Начальное и конечное значения инициализации относятся к глобальным координатам из (1), а начальная анимация

ps В C ++ на работах:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...