我有一个自定义小部件,该部件应该使点移动起来具有动画效果,以便制作一种加载小部件。为了实现这个目标,我开始使用QPainter和QVariantAnimation对象,这似乎是完成这项工作的一种不错的工具。问题是我认为绘图时初始化的QPainter相互冲突。
为此,我初始化了多个QVariantAnimation,该信号表示我将.valueChanged()连接到了一个函数update(),该函数应该会启动painEvent(),例如在文档中编写的
paint事件是重新绘制小部件的全部或一部分的请求。可能由于以下原因之一而发生: repaint()或update(), 该小部件已被遮盖,现在已被发现,或者 许多其他原因。
由于我在不同的时间开始了不同的动画,所以我假设update()被调用了很多次,从而干扰了另一个已经工作的QPainter。但是,正如我在文档中看到的那样,
当多次调用update()或窗口系统发送多个绘制事件时,Qt会将这些事件合并为一个具有较大区域的事件。
但是它没有指定任何ID,QPainter具有相同的区域,这就是为什么我认为它崩溃的原因。它记录以下消息:
QBackingStore::endPaint() called with active painter on backingstore paint device
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QDialog, QPushButton
from PyQt5.QtCore import Qt, pyqtSlot, QVariantAnimation, QVariant, QTimer
from PyQt5.QtGui import QColor, QPainter, QBrush
import time
class Dialog(QDialog):
def __init__(self, *args, **kwargs):
QDialog.__init__(self, *args, **kwargs)
self.resize(500, 500)
self.setLayout(QVBoxLayout())
self.button = QPushButton()
self.layout().addWidget(self.button)
self.paintWidget = PaintWidget()
self.layout().addWidget(self.paintWidget)
self.button.clicked.connect(self.paintWidget.startPainting)
self.button.clicked.connect(self.reverse)
def reverse(self):
if self.paintWidget.isMoving:
self.paintWidget.stopPainting()
class PaintWidget(QWidget):
def __init__(self):
super(PaintWidget, self).__init__()
self.dotRadius = 10
self.dotColor = QColor(255, 100, 100)
self.numberOfDots = 3
self.isMoving = False
self.animation = []
self.createAnimation()
self.dotPosition = [[0, 0], [0, 0], [0, 0]]
def startPainting(self):
for i in range(self.numberOfDots):
self.animation[i].start()
time.sleep(200)
self.isActive = True
def createAnimation(self):
for i in range(self.numberOfDots):
self.animation.append(QVariantAnimation(self, startValue=0, endValue=500, duration=3000))
self.animation[i].valueChanged.connect(self.updatePosition)
@pyqtSlot(QVariant)
def updatePosition(self, position):
self.dotPosition = [position, 0]
self.update()
def paintEvent(self, event):
painter = QPainter(self)
painter.fillRect(self.rect(), Qt.transparent)
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setPen(Qt.NoPen)
for i in range(self.numberOfDots):
painter.save()
painter.translate(0, 0)
position = (self.dotPosition[i][0], self.dotPosition[i][1])
color = self.dotColor
painter.setBrush(QBrush(color, Qt.SolidPattern))
painter.drawEllipse(position[0], position[1], self.dotRadius, self.dotRadius)
painter.restore()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
dial = Dialog()
dial.show()
sys.exit(app.exec_())
我知道目前该代码无法使用,因为我无法获取更新了哪个点的动画,但是我相信这里的主要问题是画家之间的干涉。有人告诉我为什么这会困扰我,并指出潜在的解决方案吗?另外,为了知道更新的点并选择了合适的位置,我真的不确定如何执行此操作。
答案 0 :(得分:2)
您的代码具有以下错误:
切勿在主GUI线程中使用time.sleep(),因为它会阻止生成应用程序冻结的事件循环。
变量dotPosition必须存储您要替换的所有位置,而在updatePosition方法中该位置只能用一个位置替换。
如果要存储位置而不是列表,则应使用QPoint,使用列表还不错,但是使用QPoint可使代码更具可读性。
不要不必要地使用painter.save()和painter.restore(),也不要使用painter.translate()。
考虑到上述情况,解决方案如下:
from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets
class Dialog(QtWidgets.QDialog):
def __init__(self, *args, **kwargs):
super(Dialog, self).__init__(*args, **kwargs)
self.resize(500, 500)
self.button = QtWidgets.QPushButton()
self.paintWidget = PaintWidget()
self.button.clicked.connect(self.paintWidget.startPainting)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.button)
lay.addWidget(self.paintWidget)
class PaintWidget(QtWidgets.QWidget):
def __init__(self):
super(PaintWidget, self).__init__()
self.dotRadius = 10
self.dotColor = QtGui.QColor(255, 100, 100)
self.animations = []
self.dotPosition = [
QtCore.QPoint(0, 0),
QtCore.QPoint(0, 0),
QtCore.QPoint(0, 0),
]
self.createAnimation()
def startPainting(self):
for i, animation in enumerate(self.animations):
QtCore.QTimer.singleShot(i * 200, animation.start)
def createAnimation(self):
for i, _ in enumerate(self.dotPosition):
wrapper = partial(self.updatePosition, i)
animation = QtCore.QVariantAnimation(
self,
startValue=0,
endValue=500,
duration=3000,
valueChanged=wrapper,
)
self.animations.append(animation)
@QtCore.pyqtSlot(int, QtCore.QVariant)
def updatePosition(self, i, position):
self.dotPosition[i] = QtCore.QPoint(position, 0)
self.update()
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.fillRect(self.rect(), QtCore.Qt.transparent)
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(
QtGui.QBrush(self.dotColor, QtCore.Qt.SolidPattern)
)
for position in self.dotPosition:
painter.drawEllipse(position, self.dotRadius, self.dotRadius)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
dial = Dialog()
dial.show()
sys.exit(app.exec_())