动画框架弹出应用程序的底部而不缩小其他元素的高度
Animating a frame to pop out of the bottom of the app without shrinking the height of other elements
我正在使用 PyQt 和 Qt creator 为 windows 开发桌面应用程序。
我想要的
我只想在用户输入时向用户显示消息。我还希望消息能吸引眼球,所以我将采用以下动画解决方案:
不需要时隐藏的框架(高度 = 0,宽度 = 应用程序的宽度),'grows' 在需要时从应用程序底部隐藏,保持可见 5-6 秒,然后缩回到底部。
没有消息的应用看起来像这样:
显示消息时有点像这样(请注意消息底部的灰色元素 'covered'):
我试过的
所以我这样做的方法是创建我称之为“页脚框架”的东西,它包含另一个我称之为“消息框架”的框架。消息框架包含一个标签,该标签将及时为用户保存消息。所有东西都有预先确定的高度,所以为了隐藏整个东西,我将消息框架的最大高度设置为 0。
因此,对于 'growing' 动画,我对消息框的最大高度 属性 进行了动画处理。
当前问题
事情是这样的 - 因为我希望应用程序能够响应,所以我将所有内容都放在布局中......因此,无论何时显示消息,其余组件的高度都是 'compressed'。
有点像这样(注意底部的灰色元素是如何没有被消息覆盖的,但是所有元素的高度都缩小了一点):
相反,我希望消息发送到 'cover' 消息坐标下的任何位置。
我试图为消息框的几何图形设置动画,但实际上什么也没有发生——可能是因为最小高度仍然是 0。所以我试图在动画开始之前更改最小高度;但这又导致了压缩。
尝试对页脚框架执行相同的操作,结果相同。
我的问题是:使用 Qt 实现我想要的结果的最佳/首选方法是什么?
布局管理员总是尝试显示他们正在管理的所有 小部件。如果你想让一个小部件与其他小部件重叠,你不能将它放在布局中,你只需创建具有父级的小部件,并且该父级可能是包含上面 或 布局的小部件顶级 window.
这不能在 Designer/Creator 中完成,因为假定一旦为父窗口小部件设置了布局,所有子窗口小部件都将由该布局管理。唯一的解决方案是以编程方式执行此操作。
在下面的示例中,我假设使用了 QMainWindow,因此参考父窗口小部件实际上是 中央窗口小部件,而不是 QMainWindow:那是因为警报应该 不 覆盖作为主要 window 布局一部分的其他小部件,例如状态栏或底部放置的工具栏或停靠栏。
动画其实是一个QSequentialAnimationGroup that shows the rectangle, waits a few seconds, and hides it again. Since the window could be resized while the animation is running, a helper function is used to properly update the start and end values of the warning and eventually update the geometry when in the "paused" state (which is actually a QPauseAnimation);为此,在中央小部件上安装了一个事件过滤器。
from random import randrange
from PyQt5 import QtCore, QtWidgets, uic
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi('overlay.ui', self)
self.alerts = []
self.centralWidget().installEventFilter(self)
self.pushButton.clicked.connect(self.showAlert)
QtCore.QTimer.singleShot(2000, self.showAlert)
def showAlert(self, message=None, timeout=250):
# create an alert that is a child of the central widget
alert = QtWidgets.QLabel(message or 'Some message to the user',
self.centralWidget(), wordWrap=True,
alignment=QtCore.Qt.AlignCenter,
styleSheet='background: rgb({}, {}, {});'.format(
randrange(192, 255), randrange(192, 255), randrange(192, 255)))
self.alerts.append(alert)
alert.animation = QtCore.QSequentialAnimationGroup(alert)
alert.animation.addAnimation(QtCore.QPropertyAnimation(
alert, b'geometry', duration=timeout))
alert.animation.addAnimation(QtCore.QPauseAnimation(3000))
alert.animation.addAnimation(QtCore.QPropertyAnimation(
alert, b'geometry', duration=timeout))
# delete the alert when the animation finishes
def deleteLater():
self.alerts.remove(alert)
alert.deleteLater()
alert.animation.finished.connect(deleteLater)
# update all animations, including the new one; this is not very
# performant, as it also updates all existing alerts; it is
# just done for simplicity;
self.updateAnimations()
# set the start geometry of the alert, show it, and start
# the new animation
alert.setGeometry(alert.animation.animationAt(0).startValue())
alert.show()
alert.animation.start()
def updateAnimations(self):
width = self.centralWidget().width() - 20
y = self.centralWidget().height()
margin = self.fontMetrics().height() * 2
for alert in self.alerts:
height = alert.heightForWidth(width) + margin
startRect = QtCore.QRect(10, y, width, height)
endRect = startRect.translated(0, -height)
alert.animation.animationAt(0).setStartValue(startRect)
alert.animation.animationAt(0).setEndValue(endRect)
alert.animation.animationAt(2).setStartValue(endRect)
alert.animation.animationAt(2).setEndValue(startRect)
def eventFilter(self, obj, event):
if obj == self.centralWidget() and event.type() == event.Resize and self.alerts:
self.updateAnimations()
for alert in self.alerts:
ani = alert.animation
# if the animation is "paused", update the geometry
if isinstance(ani.currentAnimation(), QtCore.QPauseAnimation):
alert.setGeometry(ani.animationAt(0).endValue())
return super().eventFilter(obj, event)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec())
我正在使用 PyQt 和 Qt creator 为 windows 开发桌面应用程序。
我想要的
我只想在用户输入时向用户显示消息。我还希望消息能吸引眼球,所以我将采用以下动画解决方案:
不需要时隐藏的框架(高度 = 0,宽度 = 应用程序的宽度),'grows' 在需要时从应用程序底部隐藏,保持可见 5-6 秒,然后缩回到底部。
没有消息的应用看起来像这样:
显示消息时有点像这样(请注意消息底部的灰色元素 'covered'):
我试过的
所以我这样做的方法是创建我称之为“页脚框架”的东西,它包含另一个我称之为“消息框架”的框架。消息框架包含一个标签,该标签将及时为用户保存消息。所有东西都有预先确定的高度,所以为了隐藏整个东西,我将消息框架的最大高度设置为 0。
因此,对于 'growing' 动画,我对消息框的最大高度 属性 进行了动画处理。
当前问题
事情是这样的 - 因为我希望应用程序能够响应,所以我将所有内容都放在布局中......因此,无论何时显示消息,其余组件的高度都是 'compressed'。 有点像这样(注意底部的灰色元素是如何没有被消息覆盖的,但是所有元素的高度都缩小了一点):
相反,我希望消息发送到 'cover' 消息坐标下的任何位置。
我试图为消息框的几何图形设置动画,但实际上什么也没有发生——可能是因为最小高度仍然是 0。所以我试图在动画开始之前更改最小高度;但这又导致了压缩。 尝试对页脚框架执行相同的操作,结果相同。
我的问题是:使用 Qt 实现我想要的结果的最佳/首选方法是什么?
布局管理员总是尝试显示他们正在管理的所有 小部件。如果你想让一个小部件与其他小部件重叠,你不能将它放在布局中,你只需创建具有父级的小部件,并且该父级可能是包含上面 或 布局的小部件顶级 window.
这不能在 Designer/Creator 中完成,因为假定一旦为父窗口小部件设置了布局,所有子窗口小部件都将由该布局管理。唯一的解决方案是以编程方式执行此操作。
在下面的示例中,我假设使用了 QMainWindow,因此参考父窗口小部件实际上是 中央窗口小部件,而不是 QMainWindow:那是因为警报应该 不 覆盖作为主要 window 布局一部分的其他小部件,例如状态栏或底部放置的工具栏或停靠栏。
动画其实是一个QSequentialAnimationGroup that shows the rectangle, waits a few seconds, and hides it again. Since the window could be resized while the animation is running, a helper function is used to properly update the start and end values of the warning and eventually update the geometry when in the "paused" state (which is actually a QPauseAnimation);为此,在中央小部件上安装了一个事件过滤器。
from random import randrange
from PyQt5 import QtCore, QtWidgets, uic
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi('overlay.ui', self)
self.alerts = []
self.centralWidget().installEventFilter(self)
self.pushButton.clicked.connect(self.showAlert)
QtCore.QTimer.singleShot(2000, self.showAlert)
def showAlert(self, message=None, timeout=250):
# create an alert that is a child of the central widget
alert = QtWidgets.QLabel(message or 'Some message to the user',
self.centralWidget(), wordWrap=True,
alignment=QtCore.Qt.AlignCenter,
styleSheet='background: rgb({}, {}, {});'.format(
randrange(192, 255), randrange(192, 255), randrange(192, 255)))
self.alerts.append(alert)
alert.animation = QtCore.QSequentialAnimationGroup(alert)
alert.animation.addAnimation(QtCore.QPropertyAnimation(
alert, b'geometry', duration=timeout))
alert.animation.addAnimation(QtCore.QPauseAnimation(3000))
alert.animation.addAnimation(QtCore.QPropertyAnimation(
alert, b'geometry', duration=timeout))
# delete the alert when the animation finishes
def deleteLater():
self.alerts.remove(alert)
alert.deleteLater()
alert.animation.finished.connect(deleteLater)
# update all animations, including the new one; this is not very
# performant, as it also updates all existing alerts; it is
# just done for simplicity;
self.updateAnimations()
# set the start geometry of the alert, show it, and start
# the new animation
alert.setGeometry(alert.animation.animationAt(0).startValue())
alert.show()
alert.animation.start()
def updateAnimations(self):
width = self.centralWidget().width() - 20
y = self.centralWidget().height()
margin = self.fontMetrics().height() * 2
for alert in self.alerts:
height = alert.heightForWidth(width) + margin
startRect = QtCore.QRect(10, y, width, height)
endRect = startRect.translated(0, -height)
alert.animation.animationAt(0).setStartValue(startRect)
alert.animation.animationAt(0).setEndValue(endRect)
alert.animation.animationAt(2).setStartValue(endRect)
alert.animation.animationAt(2).setEndValue(startRect)
def eventFilter(self, obj, event):
if obj == self.centralWidget() and event.type() == event.Resize and self.alerts:
self.updateAnimations()
for alert in self.alerts:
ani = alert.animation
# if the animation is "paused", update the geometry
if isinstance(ani.currentAnimation(), QtCore.QPauseAnimation):
alert.setGeometry(ani.animationAt(0).endValue())
return super().eventFilter(obj, event)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec())