更改线程中的QObject样式表时出错

时间:2019-06-06 19:20:22

标签: python multithreading pyqt pyqt5 qtstylesheets

上下文

我想用python构建QObject动画。例如,我尝试对QLineEdit对象的背景进行动画处理,以便在输入错误时产生“红色闪烁”。该函数正在运行,线程开始运行,并且我看到了动画,但是当线程结束时,应用程序崩溃,而没有错误回溯。我只有

exit code -1073740940

我在互联网上找不到的那个

最小工作示例

这里是我为了让您仅用一个文件即可重现此错误而制作的mwe。您会注意到代码的重要部分在LoginDialog类中。

from PyQt5.QtWidgets import QDialog, QLineEdit, QVBoxLayout, QApplication
from threading import Thread
import time
import sys


class Ui_LoginUi(object):
    def setupUi(self, Ui_LoginUi):
        Ui_LoginUi.setObjectName("LoginUi")
        Ui_LoginUi.resize(293, 105)
        self.layout = QVBoxLayout(Ui_LoginUi)
        self.le_test = QLineEdit(Ui_LoginUi)
        self.layout.addWidget(self.le_test)


class LoginDialog(QDialog, Ui_LoginUi):

    def __init__(self):
        super(LoginDialog, self).__init__()
        self.setupUi(self)
        self.le_test.textChanged.connect(self.redFlashThreader)

    def redFlashThreader(self):
        self.redFlashTread1 = Thread(target=self.lineEdit_redFlash, args=[self.le_test])
        self.redFlashTread1.start()

    def lineEdit_redFlash(self, *args):
        inital_r = 255
        initial_g = 127
        initial_b = 127

        for i in range(64):
            initial_g += 2
            initial_b += 2
            time.sleep(0.005)
            args[0].setStyleSheet("background-color: rgb(255,{},{})".format(initial_g, initial_b))

        args[0].setStyleSheet("background-color: rgb(255,255,255")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    dialog = LoginDialog()
    dialog.show()
    sys.exit(app.exec_())

结果

如果单击多次,该应用程序将冻结并崩溃。我想理解为什么,但是没有追溯,我觉得很难。有时,它会在第一次点击后发生。我以为这是一个线程冲突问题,但是由于只有在运行第一个线程时才会发生,所以我不太确定。任何人都可以指出正确的方向或向我解释发生了什么事?

2 个答案:

答案 0 :(得分:3)

您的问题允许分析以下方面:

1)您不应直接从另一个线程更新任何GUI元素

GUI的绘制是在主线程中完成的,因此GUI在任何情况下都不允许修改涉及从另一个线程进行绘制的任何属性,因此,如果开发人员这样做,则不能保证在这种情况下能正常工作怎么了。有关更多信息,请阅读GUI Thread and Worker Thread

对于Qt,如果要从另一个线程更新某些GUI元素,则应通过某种方式(信号,QEvent,QMetaObject :: invokeMethod()等)将信息发送到主线程,并且在主线程中进行更新。

因此,考虑到上述情况,使用信号的可能解决方案如下:

import sys
import time
from threading import Thread
from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_LoginUi(object):
    def setupUi(self, Ui_LoginUi):
        Ui_LoginUi.setObjectName("LoginUi")
        Ui_LoginUi.resize(293, 105)
        layout = QtWidgets.QVBoxLayout(Ui_LoginUi)
        self.le_test = QtWidgets.QLineEdit(Ui_LoginUi)
        layout.addWidget(self.le_test)


class LoginDialog(QtWidgets.QDialog, Ui_LoginUi):
    colorChanged = QtCore.pyqtSignal(QtGui.QColor)

    def __init__(self):
        super(LoginDialog, self).__init__()
        self.setupUi(self)
        self.le_test.textChanged.connect(self.redFlashThreader)
        self.colorChanged.connect(self.on_color_change)

    @QtCore.pyqtSlot()
    def redFlashThreader(self):
        self.redFlashTread1 = Thread(
            target=self.lineEdit_redFlash, args=[self.le_test]
        )
        self.redFlashTread1.start()

    def lineEdit_redFlash(self, *args):
        inital_r = 255
        initial_g = 127
        initial_b = 127

        for i in range(64):
            initial_g += 2
            initial_b += 2
            time.sleep(0.005)
            self.colorChanged.emit(QtGui.QColor(255, initial_g, initial_b))
        self.colorChanged.emit(QtGui.QColor(255, 255, 255))

    @QtCore.pyqtSlot(QtGui.QColor)
    def on_color_change(self, color):
        self.setStyleSheet("QLineEdit{background-color: %s}" % (color.name(),))

        """ or
        self.setStyleSheet(
            "QLineEdit{ background-color: rgb(%d, %d, %d)}"
            % (color.red(), color.green(), color.blue())
        )"""

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    dialog = LoginDialog()
    dialog.show()
    sys.exit(app.exec_())

2)不必使用线程在Qt中制作动画,而应使用QVariantAnimation,QPropertyAnimation等。

在GUI中,应该避免使用线程,因为它带来的问题多于好处(例如,使信号队列饱和),因此请尽量使用它。在这种情况下,您可以使用QVariantAnimationQPropertyAnimation

2.1)QVariantAnimation

import sys
from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_LoginUi(object):
    def setupUi(self, Ui_LoginUi):
        Ui_LoginUi.setObjectName("LoginUi")
        Ui_LoginUi.resize(293, 105)
        layout = QtWidgets.QVBoxLayout(Ui_LoginUi)
        self.le_test = QtWidgets.QLineEdit(Ui_LoginUi)
        layout.addWidget(self.le_test)


class LoginDialog(QtWidgets.QDialog, Ui_LoginUi):
    def __init__(self):
        super(LoginDialog, self).__init__()
        self.setupUi(self)
        self.le_test.textChanged.connect(self.start_animation)

        self.m_animation = QtCore.QVariantAnimation(
            self,
            startValue=QtGui.QColor(255, 127, 127),
            endValue=QtGui.QColor(255, 255, 255),
            duration=1000,
            valueChanged=self.on_color_change,
        )

    @QtCore.pyqtSlot()
    def start_animation(self):
        if self.m_animation.state() == QtCore.QAbstractAnimation.Running:
            self.m_animation.stop()
        self.m_animation.start()

    @QtCore.pyqtSlot(QtCore.QVariant)
    @QtCore.pyqtSlot(QtGui.QColor)
    def on_color_change(self, color):
        self.setStyleSheet("QLineEdit{background-color: %s}" % (color.name(),))

        """ or
        self.setStyleSheet(
            "QLineEdit{ background-color: rgb(%d, %d, %d)}"
            % (color.red(), color.green(), color.blue())
        )"""


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    dialog = LoginDialog()
    dialog.show()
    sys.exit(app.exec_())

2.2)QPropertyAnimation

import sys
from PyQt5 import QtCore, QtGui, QtWidgets


class LineEdit(QtWidgets.QLineEdit):
    backgroundColorChanged = QtCore.pyqtSignal(QtGui.QColor)

    def backgroundColor(self):
        if not hasattr(self, "_background_color"):
            self._background_color = QtGui.QColor()
            self.setBackgroundColor(QtGui.QColor(255, 255, 255))
        return self._background_color

    def setBackgroundColor(self, color):
        if self._background_color != color:
            self._background_color = color
            self.setStyleSheet("background-color: {}".format(color.name()))
            self.backgroundColorChanged.emit(color)

    backgroundColor = QtCore.pyqtProperty(
        QtGui.QColor,
        fget=backgroundColor,
        fset=setBackgroundColor,
        notify=backgroundColorChanged,
    )


class Ui_LoginUi(object):
    def setupUi(self, Ui_LoginUi):
        Ui_LoginUi.setObjectName("LoginUi")
        Ui_LoginUi.resize(293, 105)
        layout = QtWidgets.QVBoxLayout(Ui_LoginUi)
        self.le_test = LineEdit(Ui_LoginUi)
        layout.addWidget(self.le_test)


class LoginDialog(QtWidgets.QDialog, Ui_LoginUi):
    def __init__(self):
        super(LoginDialog, self).__init__()
        self.setupUi(self)
        self.le_test.textChanged.connect(self.start_animation)

        self.m_animation = QtCore.QPropertyAnimation(
            self.le_test,
            b'backgroundColor',
            self,
            startValue=QtGui.QColor(255, 127, 127),
            endValue=QtGui.QColor(255, 255, 255),
            duration=1000,
        )

    @QtCore.pyqtSlot()
    def start_animation(self):
        if self.m_animation.state() == QtCore.QAbstractAnimation.Running:
            self.m_animation.stop()
        self.m_animation.start()


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    dialog = LoginDialog()
    dialog.show()
    sys.exit(app.exec_())

答案 1 :(得分:2)

enter image description here

要在输入错误时实现“红色闪烁”,可以使用QTimer.singleShot()。本质上,当在字段中更改文本并触发错误的文本时,可以将背景更改为错误颜色。然后经过一定时间(例如2秒后),您可以重置字段颜色。

from PyQt5.QtWidgets import QDialog, QLineEdit, QVBoxLayout, QApplication
from PyQt5.QtCore import QTimer 
import time
import sys

class Ui_LoginUi(object):
    def setupUi(self, Ui_LoginUi):
        Ui_LoginUi.setObjectName("LoginUi")
        Ui_LoginUi.resize(293, 105)
        self.layout = QVBoxLayout(Ui_LoginUi)
        self.le_test = QLineEdit(Ui_LoginUi)
        self.layout.addWidget(self.le_test)

class LoginDialog(QDialog, Ui_LoginUi):

    def __init__(self):
        super(LoginDialog, self).__init__()
        self.setupUi(self)

        self.invalid_color = 'background-color: #c91d2e'
        self.valid_color = 'background-color: #FFF'
        self.le_test.textChanged.connect(self.redFlashHandler)

    def redFlashHandler(self):
        if self.le_test.text() == 'test':
            self.le_test.setStyleSheet(self.invalid_color)

            # After 2000 ms, reset field color
            QTimer.singleShot(2000, self.resetFieldColor)

    def resetFieldColor(self):
        self.le_test.setStyleSheet(self.valid_color)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    dialog = LoginDialog()
    dialog.show()
    sys.exit(app.exec_())