QPainters同步背后的机制

2024-09-27 21:28:41 发布

您现在位置:Python中文网/ 问答频道 /正文

上下文

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

技术

为了实现这一点,我初始化了多个QVariantAnimation,我将signal.valueChanged()连接到一个函数update(),该函数应该启动painEvent(),比如写在文档中的

A paint event is a request to repaint all or part of a widget. It can happen for one of the following reasons:repaint() or update() was invoked, the widget was obscured and has now been uncovered, or many other reasons.

因为我在不同的时间启动不同的动画,所以我假设update()被多次调用,从而干扰另一个已经工作的QPainter。但是,正如我在文件中看到的

When update() is called several times or the window system sends several paint events, Qt merges these events into one event with a larger region.

但是它没有指定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_())

结果

我知道现在这个代码不起作用,因为我无法检索哪个点的动画更新了,但是我相信这里的主要问题是画家之间的干扰。因此,有人能告诉我为什么会出现这种情况,并为我指出一个可能的解决方案吗?另外,为了知道更新的点和选择的好位置,我真的不确定如何做到这一点。你知道吗


Tags: orimportselfforinitdefpositionupdate
1条回答
网友
1楼 · 发布于 2024-09-27 21:28:41

您的代码有以下错误:

  • 从不使用时间。睡眠(),因为它阻止了生成应用程序冻结的事件循环。

  • 变量dotPosition必须存储所有要替换的位置,在updatePosition方法中只能使用一个位置。

  • 如果要存储位置而不是列表,那么应该使用QPoint,使用列表也不错,但是使用QPoint可以使代码更具可读性。

  • 不要使用油漆工.保存()和油漆工.恢复()不必要的,也不是油漆工.翻译().

综上所述,解决方案如下:

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

相关问题 更多 >

    热门问题