首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >QPainters同步背后的机制

QPainters同步背后的机制
EN

Stack Overflow用户
提问于 2019-06-09 16:59:06
回答 1查看 245关注 0票数 1

上下文

我有一个定制的widget,它用来制作点移动的动画,以制作一种加载小部件。为了实现这个目标,我开始使用QPainter和QVariantAnimation对象,这似乎是完成任务的一个不错的工具。问题是,我认为我在绘图时初始化的QPainters彼此冲突。

技术

为此,我初始化了多个QVariantAnimation,它向.valueChanged()发出信号,连接到函数update(),函数update()应该启动painEvent(),例如在文档中写入

画图事件是重新绘制小部件的全部或部分的请求。发生这种情况可能有以下原因之一:调用了重绘()或update(),小部件被蒙蔽,现在已被发现,或许多其他原因。

由于我在不同的时间启动了不同的动画,所以我认为update()被多次调用,从而干扰了另一个已经工作的QPainter。但是,当我在文档里读到,

当update()被多次调用或窗口系统发送了几个画图事件时,Qt将这些事件合并为一个具有较大区域的事件。

但是它没有指定QPainter有相同的区域,这就是为什么我认为它崩溃的原因。它记录的消息如下:

代码语言:javascript
复制
QBackingStore::endPaint() called with active painter on backingstore paint device

最小工作实例

代码语言:javascript
复制
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_())

结果

我知道这段代码现在无法工作,因为我无法收回哪个点的动画被更新了,但是,我相信这里的主要问题是画家之间的干扰。因此,,谁能告诉我为什么这样做,并为我指出一个潜在的解决方案?另外,为了知道更新的点并选择好的位置,我也不知道该如何做。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-06-09 17:44:57

您的代码有以下错误:

  • 不要在主GUI线程中使用time.sleep(),因为它阻止事件循环,生成应用程序的冻结。
  • 变量dotPosition,它必须存储您要替换的所有位置,在updatePosition方法中只有一个位置。
  • 如果要存储位置而不是列表,则应该使用QPoint,使用列表并不坏,但使用QPoint可以提高代码的可读性。
  • 不要不必要地使用painter.save()和painter.restore(),也不要使用painter.translate()。

考虑到上述情况,解决办法如下:

代码语言:javascript
复制
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_())
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/56516618

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档