我想用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_())
如果单击多次,该应用程序将冻结并崩溃。我想理解为什么,但是没有追溯,我觉得很难。有时,它会在第一次点击后发生。我以为这是一个线程冲突问题,但是由于只有在运行第一个线程时才会发生,所以我不太确定。任何人都可以指出正确的方向或向我解释发生了什么事?
答案 0 :(得分:3)
您的问题允许分析以下方面:
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_())
在GUI中,应该避免使用线程,因为它带来的问题多于好处(例如,使信号队列饱和),因此请尽量使用它。在这种情况下,您可以使用QVariantAnimation
或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_())
答案 1 :(得分:2)
要在输入错误时实现“红色闪烁”,可以使用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_())