将动态小部件对象传递给 QVariantAnimation valueChanged 信号处理程序

Pass in dynamic widget object to QVariantAnimation valueChanged signal handler

我正在尝试制作一个无效的动画工具。我有一个按钮,按下该按钮时,会将字段的背景颜色从红色动画化为“正常”字段颜色。这很好用,但现在我想传入任何 arbitrary PyQt 小部件对象(可以是 QLineEditQComboBox,等等)。这是我的动画处理程序的样子:

@QtCore.pyqtSlot(QtGui.QColor)
def invalid_animation_handler(color: QtGui.QColor) -> None:
    field.setStyleSheet(f"background-color: {QtGui.QColor(color).name()}")

目前要求field对象在调用函数改变背景之前已经命名。我希望能够动态传递 widget 并通过传递参数动态设置样式表,如下所示:

@QtCore.pyqtSlot(QtGui.QColor)
def invalid_animation_handler(widget, color: QtGui.QColor) -> None:
    widget.setStyleSheet(f"background-color: {QtGui.QColor(color).name()}")

当我尝试这样做时,小部件可以通过,但是从 WARNING_COLORNORMAL_COLOR 的恒定颜色变化不再起作用。我也 不想将它放入 class 中,因为它必须是即时的。我的目标是能够从任何地方调用一个函数来启动动画,而不必按下按钮。想要的目标是这样的:

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)

最小工作示例

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_())

我不明白你为什么不想要 class 但 IMO 是最合适的解决方案。逻辑是存储允许更改 属性 的可调用对象并在 updateCurrentValue.

中调用它

目前我没有安装 PyQt4,所以我用 PyQt5 实现了逻辑,但我认为更改导入并不困难。

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_())