Ошибка при изменении таблицы стилей QObject в потоке - PullRequest
2 голосов
/ 06 июня 2019

Контекст

Я хочу создавать анимации QObject в python.Например, я попытался анимировать фон объекта QLineEdit, чтобы сделать «красную вспышку» при вводе чего-то неправильного.Функция работает, поток запускается, и я вижу анимацию, но когда поток заканчивается, приложение сворачивается без отслеживания ошибок.Я получаю только

exit code -1073740940

которого я не нашел в интернете.

Минимальный рабочий пример

Вот мве, которое я сделал для того, чтобы вы воспроизвели этоошибка только с одним файлом.Вы заметите, что важная часть кода находится внутри класса 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 ]

3 голосов
/ 06 июня 2019

Ваш вопрос позволяет проанализировать следующие аспекты:

1) Вы не должны напрямую обновлять какой-либо элемент GUI из другого потока

Рисование GUI выполняется в основном потоке, поэтомуГрафический интерфейс пользователя не позволяет ни в коем случае изменять какое-либо свойство, которое включает рисование из другого потока, поэтому, если разработчик делает это, нет гарантии, что в этом случае что-то не так.Для получения дополнительной информации читайте Поток GUI и рабочий поток .

. В случае 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 и т.д.проблемы, чем выгоды (например, насыщать сигнальную очередь), поэтому используйте его в качестве крайней меры.В этом случае вы можете использовать QVariantAnimation или QPropertyAnimation: 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_())

2 голосов
/ 06 июня 2019

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