pyqt上的QGraphicsEffect,闪烁一个按钮

QGraphicsEffect on pyqt, blinking a button

我正在 python 和 pyqt 上构建一个 GUI。 GUI 有很多按钮,通过 class LED 生成,这意味着每个 LED 有 3 个按钮,用于 n 个 LED。

在一些按钮中,我想要一个改变按钮不透明度的效果,在从 0 到 1 的循环中再返回,所以它会消失并出现。我只需要一个进程来管理所有,所以每个按钮的效果同时开始,所有按钮同时闪烁。

我已经设法通过线程中的 qgraphicseffect 遍历列表来实现这一点。 问题是几分钟后效果停止,尽管线程仍然是 运行 (print(opacity_level))。更多具有效果的按钮使持续时间更短。单击任何按钮,即使其他按钮无效,也会重新启动 gui 动画。

我对 pyqt 线程的小研究让我实现了这个线程管理器,虽然我并不完全理解它。

class WorkerSignals(QtCore.QObject):
    finished = QtCore.pyqtSignal()
    error = QtCore.pyqtSignal(tuple)
    result = QtCore.pyqtSignal(object)
    progress = QtCore.pyqtSignal(tuple)

class Worker(QtCore.QRunnable):
    '''
    Worker thread
    Inherits from QRunnable to handler worker thread setup, signals and wrap-up.
    '''
    def __init__(self, fn, *args, **kwargs):
        super(Worker, self).__init__()
        # Store constructor arguments (re-used for processing)
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()

        # Add the callback to our kwargs
        self.kwargs['progress_callback'] = self.signals.progress

    @pyqtSlot()
    def run(self):
        '''
        Initialise the runner function with passed args, kwargs.
        '''
        # Retrieve args/kwargs here; and fire processing using them
        try:
            result = self.fn(*self.args, **self.kwargs)
        except:
            traceback.print_exc()
            exctype, value = sys.exc_info()[:2]
            self.signals.error.emit((exctype, value, traceback.format_exc()))
        else:
            self.signals.result.emit(result)  # Return the result of the processing
        finally:
            self.signals.finished.emit()  # Done

接下来是 LED class

class LEDs:
    def __init__(self,name,group,frame):
        self.opacity_effect = QtWidgets.QGraphicsOpacityEffect()
        self.button_auto = QtWidgets.QPushButton()
        self.button_auto.setObjectName("button_auto_neutral")
        self.button_auto.clicked.connect(lambda state, x=self: self.AutoMode())
    
    def AutoMode(self):
        print(self.name,"Automode")
        if len(settings.blink) ==0: # start thread only if no previous thread, both thread and 
                                        this reference the size of settings.blink, so not ideal.
            print("start thread")
            settings.ledAutomode()

        settings.blink.append(self)

最后是设置 class,其中包含具有执行操作效果的线程。还有第二个线程,它处理按钮的图标,相应地有一个时间表。

class Settings:
    def __init__(self):
        self.blink=[]

    def ledAutomode(self):

        def blink(progress_callback):
            print("opacity")
            op_up=[x/100 for x in range(0,101,5)]
            op_down=op_up[::-1]; op_down=op_down[1:-1]; opacity=op_up+op_down

            while len(self.blink) !=0:
                for i in opacity:
                    print(i)
                    QtCore.QThread.msleep(80)
                    for led in self.blink:
                        led.opacity_effect.setOpacity(i)

        def timeCheck(progress_callback):
            while len(self.blink) != 0:
                QtCore.QThread.msleep(500)
                for led in self.blink:
                    matrix = [v for v in settings.leds_config[led.group][led.name]["Timetable"]]
                    matrix_time=[]

                    ...
                    # some code
                    ...

                    if sum(led_on_time):
                        led.button_auto.setObjectName("button_auto_on")
                        led.button_auto.setStyleSheet(ex.stylesheet)

                    else:
                        led.button_auto.setObjectName("button_auto_off")
                        led.button_auto.setStyleSheet(ex.stylesheet)
                    QtCore.QThread.msleep(int(30000/len(self.blink)))


        worker = Worker(blink)  # Any other args, kwargs are passed to the run function
        ex.threadpool.start(worker)
        worker2 = Worker(timeCheck)  # Any other args, kwargs are passed to the run function
        ex.threadpool.start(worker2)

所以,也许是对 qgraphicseffect 的限制,或者线程有问题(尽管它一直在打印),或者我犯了一些错误。

我读过有关 subclassing qgraphicseffect 的内容,但我不知道这是否能解决问题。 如果有人有其他实现,总是渴望学习。

感谢您的宝贵时间。

小部件 不是 thread-safe。
它们不能从外部线程创建或访问。虽然它“有时”有效,但这样做是错误的,通常会导致意外行为、绘图伪影甚至致命崩溃。

就是说,您正在使整个过程令人难以置信和不必要地错综复杂,比它应该的要复杂得多,最重要的是因为 Qt 已经提供了定时事件 (QTimer) 和 animations

class FadeButton(QtWidgets.QPushButton):
    def __init__(self):
        super().__init__()
        self.effect = QtWidgets.QGraphicsOpacityEffect(opacity=1.0)
        self.setGraphicsEffect(self.effect)
        self.animation = QtCore.QPropertyAnimation(self.effect, b'opacity')
        self.animation.setStartValue(1.0)
        self.animation.setEndValue(0.0)
        self.animation.setDuration(1500)
        self.animation.finished.connect(self.checkAnimation)

        self.clicked.connect(self.startAnimation)

    def startAnimation(self):
        self.animation.stop()
        self.animation.setDirection(self.animation.Forward)
        self.animation.start()

    def checkAnimation(self):
        if not self.animation.value():
            self.animation.setDirection(self.animation.Backward)
            self.animation.start()
        else:
            self.animation.setDirection(self.animation.Forward)

如果您想在许多小部件之间同步不透明度,有多种可能性,但更新所有不透明度的 QVariantAnimation 可能是更容易的选择:

class LEDs(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        layout = QtWidgets.QHBoxLayout(self)

        self.animation = QtCore.QVariantAnimation()
        self.animation.setStartValue(1.0)
        self.animation.setEndValue(0.0)
        self.animation.setDuration(1500)
        self.animation.valueChanged.connect(self.updateOpacity)
        self.animation.finished.connect(self.checkAnimation)

        self.buttons = []
        for i in range(3):
            button = QtWidgets.QPushButton()
            self.buttons.append(button)
            layout.addWidget(button)
            effect = QtWidgets.QGraphicsOpacityEffect(opacity=1.0)
            button.setGraphicsEffect(effect)
            button.clicked.connect(self.startAnimation)

    # ... as above ...

    def updateOpacity(self, opacity):
        for button in self.buttons:
            button.graphicsEffect().setOpacity(opacity)

请注意,您不应在运行时更改小部件的对象名称,仅仅因为要更新样式表而这样做是错误的。您要么使用不同的样式表,要么使用 属性 选择器:

    QPushButton {
        /* default state */
        background: #ababab;
    }
    QPushButton[auto_on="true"] {
        /* "on" state */
        background: #dadada;
    }
class FadeButton(QtWidgets.QPushButton):
    def __init__(self):
        super().__init__()
        # ...
        self.setProperty('auto_on', False)

    def setAuto(self, state):
        self.setProperty('auto_on', state)
        self.setStyleSheet(self.styleSheet())