Механика синхронизации QPainters - PullRequest
1 голос
/ 09 июня 2019

Контекст

У меня есть собственный виджет, который должен создавать анимацию перемещения точек для создания виджета загрузки. Чтобы достичь этой цели, я начал использовать объекты QPainter и QVariantAnimation, которые казались достойными инструментами для работы. Проблема в том, что я думаю, что QPainters, которые я инициализирую при рисовании, конфликтуют друг с другом.

Техника

Для этого я инициализирую несколько QVariantAnimation, сигнал которых .valueChanged () подключаю к функции update (), которая должна запускать painEvent (), например, записанную в документации

Событие рисования - это запрос на перерисовку всего или части виджета. Это может произойти по одной из следующих причин: repaint () или update () был вызван, виджет был скрыт и теперь обнаружен, или много других причин.

Поскольку я запускаю разные анимации в разное время, я полагаю, что update () вызывается много раз, что мешает работе другого QPainter. Но, как я читал в документах,

Когда update () вызывается несколько раз или оконная система отправляет несколько событий рисования, Qt объединяет эти события в одно событие с большей областью.

Но он ничего не указывает, если 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_())

Результаты

Я знаю, что на данный момент этот код не будет работать, потому что я не могу восстановить, какая анимация точки была обновлена, но Я считаю, что главная проблема здесь - это помехи между художниками. Таким образом, любой может скажите мне, почему это происходит, и укажите мне на потенциальное решение? Кроме того, зная точку, которая была обновлена ​​и выбрала хорошую позицию, я действительно не уверен, как это сделать.

1 Ответ

2 голосов
/ 09 июня 2019

В вашем коде есть следующие ошибки:

  • Никогда не используйте 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_())
...