I want to build QObject animations in python. For example, I tried animating the background of a QLineEdit object in order to make a "red flash" when a something wrong is entered. The function is working, the thread starts and I see the animation, but when the thread ends, the app collapses without error trace-back. I only get
exit code -1073740940
Which I didn't find on the internet.
Here's a mwe that I made in order for you to reproduce this error with only one file. You will notice that the important part of the code is inside LoginDialog class.
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_())
If you click multiple times, the app will freeze and crash. I would like to understand why, but without trace-back, I find that quite hard. Sometimes, it happens after the first click. I thought it would be a thread conflict problem, but since it happens with only the first thread running, I'm not so sure. Anyone could point me in the right direction or explain to me what is happening?
Your question allows to analyze the following aspects:
The painting of the GUI is done in the main thread so the GUI do not allow in any case to modify any property that involves painting from another thread, so if the developer does it there is no guarantee that works as in this case what's wrong. For more information read GUI Thread and Worker Thread .
In the case of Qt if you want to update some GUI element from another thread what you should do is send by some means (signals, QEvent, QMetaObject::invokeMethod(), etc) the information to the main thread, and in the main thread do the update.
So considering the above, a possible solution using signals is the following:
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_())
In a GUI you should avoid using threading since it can bring more problems than benefits (for example saturate the signal queue), so use it as a last resort. In this case you can use QVariantAnimation
or QPropertyAnimation
:
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_())
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_())
To achieve the 'red flash' when something wrong is entered, you can use QTimer.singleShot()
. Essentially when text is changed in the field, and it triggers your wrong text, you can change the background to the error color. Then after a certain amount of time, say 2 seconds later, you can reset the field color.
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_())
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.