I'm trying to make an invalid animation tool. I have a button that when pressed, animates the background color of a field from red to the "normal" field color. This works great but now I want to pass in any arbitrary PyQt widget object (could be QLineEdit
, QComboBox
, and so on). Here's what my animation handler looks like:
@QtCore.pyqtSlot(QtGui.QColor)
def invalid_animation_handler(color: QtGui.QColor) -> None:
field.setStyleSheet(f"background-color: {QtGui.QColor(color).name()}")
Currently it requires that the field
object be already named before calling the function to change the background. I would like to be able to dynamically pass in a widget
and set the stylesheet on the fly by passing in a parameter, something like this:
@QtCore.pyqtSlot(QtGui.QColor)
def invalid_animation_handler(widget, color: QtGui.QColor) -> None:
widget.setStyleSheet(f"background-color: {QtGui.QColor(color).name()}")
When I try to do this, the widget is able to be passed but the constant color change from WARNING_COLOR
to NORMAL_COLOR
doesn't work anymore. I also do not want to put this into a class since it has to be on-the-fly. My goal is being able to call a function to start the animation from anywhere instead of having to press the button. The desired goal is like this:
class VariantAnimation(QtCore.QVariantAnimation):
"""VariantAnimation: Implement method for QVariantAnimation to fix pure virtual method in PyQt5 -> PyQt4"""
def updateCurrentValue(self, value):
pass
@QtCore.pyqtSlot(QtGui.QColor)
def invalid_animation_handler(widget, color: QtGui.QColor) -> None:
widget.setStyleSheet(f"background-color: {QtGui.QColor(color).name()}")
def invalid_animation(widget):
return VariantAnimation(startValue=ERROR_COLOR, endValue=NORMAL_COLOR, duration=ANIMATION_DURATION, valueChanged=lambda: invalid_animation_handler(widget))
def start_invalid_animation(animation_handler) -> None:
if animation_handler.state() == QtCore.QAbstractAnimation.Running:
animation_handler.stop()
animation_handler.start()
field = QtGui.QLineEdit()
field_animation_handler = invalid_animation(field)
# Goal is to make a generic handler to start the animation
start_invalid_animation(field_animation_handler)
Minimal working example
import sys
from PyQt4 import QtCore, QtGui
class VariantAnimation(QtCore.QVariantAnimation):
"""VariantAnimation: Implement method for QVariantAnimation to fix pure virtual method in PyQt5 -> PyQt4"""
def updateCurrentValue(self, value):
pass
@QtCore.pyqtSlot(QtGui.QColor)
def invalid_animation_handler(color: QtGui.QColor) -> None:
field.setStyleSheet(f"background-color: {QtGui.QColor(color).name()}")
def start_field_invalid_animation() -> None:
if field_invalid_animation.state() == QtCore.QAbstractAnimation.Running:
field_invalid_animation.stop()
field_invalid_animation.start()
NORMAL_COLOR = QtGui.QColor(25,35,45)
SUCCESSFUL_COLOR = QtGui.QColor(95,186,125)
WARNING_COLOR = QtGui.QColor(251,188,5)
ERROR_COLOR = QtGui.QColor(247,131,128)
ANIMATION_DURATION = 1500
if __name__== '__main__':
app = QtGui.QApplication(sys.argv)
button = QtGui.QPushButton('Animate field background')
button.clicked.connect(start_field_invalid_animation)
field = QtGui.QLineEdit()
field_invalid_animation = VariantAnimation(startValue=ERROR_COLOR, endValue=NORMAL_COLOR, duration=ANIMATION_DURATION, valueChanged=invalid_animation_handler)
mw = QtGui.QMainWindow()
layout = QtGui.QHBoxLayout()
layout.addWidget(button)
layout.addWidget(field)
window = QtGui.QWidget()
window.setLayout(layout)
mw.setCentralWidget(window)
mw.show()
sys.exit(app.exec_())
CodePudding user response:
I do not understand exactly why you do not want a class but IMO is the most suitable solution. The logic is to store the callable that allows to change the property and invoke it in updateCurrentValue.
Currently I don't have PyQt4 installed so I implemented the logic with PyQt5 but I don't think it is difficult to change the imports.
import sys
from dataclasses import dataclass
from functools import partial
from typing import Callable
from PyQt5.QtCore import QAbstractAnimation, QObject, QVariant, QVariantAnimation
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import (
QApplication,
QHBoxLayout,
QLineEdit,
QMainWindow,
QPushButton,
QWidget,
)
@dataclass
class VariantAnimation(QVariantAnimation):
widget: QWidget
callback: Callable[[QWidget, QVariant], None]
start_value: QVariant
end_value: QVariant
duration: int
parent: QObject = None
def __post_init__(self) -> None:
super().__init__()
self.setStartValue(self.start_value)
self.setEndValue(self.end_value)
self.setDuration(self.duration)
self.setParent(self.parent)
def updateCurrentValue(self, value):
if isinstance(self.widget, QWidget) and callable(self.callback):
self.callback(self.widget, value)
def invalid_animation_handler(widget: QWidget, color: QColor) -> None:
widget.setStyleSheet(f"background-color: {QColor(color).name()}")
def start_field_invalid_animation(animation: QAbstractAnimation) -> None:
if animation.state() == QAbstractAnimation.Running:
animation.stop()
animation.start()
NORMAL_COLOR = QColor(25, 35, 45)
SUCCESSFUL_COLOR = QColor(95, 186, 125)
WARNING_COLOR = QColor(251, 188, 5)
ERROR_COLOR = QColor(247, 131, 128)
ANIMATION_DURATION = 1500
if __name__ == "__main__":
app = QApplication(sys.argv)
button = QPushButton("Animate field background")
field = QLineEdit()
animation = VariantAnimation(
widget=field,
callback=invalid_animation_handler,
start_value=ERROR_COLOR,
end_value=NORMAL_COLOR,
duration=ANIMATION_DURATION,
)
button.clicked.connect(partial(start_field_invalid_animation, animation))
mw = QMainWindow()
layout = QHBoxLayout()
layout.addWidget(button)
layout.addWidget(field)
window = QWidget()
window.setLayout(layout)
mw.setCentralWidget(window)
mw.show()
sys.exit(app.exec_())
CodePudding user response:
Another solution instead of using @eyllanesc's method of using partials. A workaround was to change the scope of the function and put the actual function that sets the stylesheet within the same scope as the parent function.
class VariantAnimation(QtCore.QVariantAnimation):
"""VariantAnimation: Implement method for QVariantAnimation to fix pure virtual method in PyQt5 -> PyQt4"""
def updateCurrentValue(self, value):
pass
def invalid_animation(widget):
@QtCore.pyqtSlot(QtGui.QColor)
def invalid_animation_handler(color: QtGui.QColor) -> None:
widget.setStyleSheet(f"background-color: {QtGui.QColor(color).name()}")
return VariantAnimation(startValue=ERROR_COLOR, endValue=NORMAL_COLOR, duration=ANIMATION_DURATION, valueChanged=invalid_animation_handler)
def start_invalid_animation(animation_handler) -> None:
if animation_handler.state() == QtCore.QAbstractAnimation.Running:
animation_handler.stop()
animation_handler.start()
field = QtGui.QLineEdit()
field_animation_handler = invalid_animation(field)
# Goal is to make a generic handler to start the animation
start_invalid_animation(field_animation_handler)