python - QPainters 同步背后的机制

标签 python pyqt pyqt5 qpainter qvariantanimation

上下文

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

技术

为了实现这一点,我初始化了多个 QVariantAnimation,它向 .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_())

结果

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

最佳答案

您的代码有以下错误:

  • 切勿在主 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_())

关于python - QPainters 同步背后的机制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56516618/

相关文章:

python - pyqt5对话框不显示任何小部件

python - 执行 python-scrappy 模块时出错

python - 通过python从netCDF中提取特定位置的数据

python - PyQt5循环问题,只有QWebEngineView对象的最后一次迭代有效

Python PyQt Qlabel 调整大小

python - PDF 输出不适用于 Pyqt5 和 Python 3.5

python - wheelEvent 上的水平滚动,移位太快

python - 在 QGraphicsScene 中连续移动 QGraphicsItem 并检查 Collision

python - padding_idx 在 nn.embeddings() 中做什么

python - 如何在不添加额外索引的情况下使用 Pandas groupby apply()