2
votes

I've a widget that is basically a circle. I want to draw it progressively so I need to draw it in steps (imo).

With the following code, I have achieved what I want. However, there's a problem. I'm passing a new event to the paintEvent function, because if I don't, the image doesn't get updated until everything is finished, so I achieve nothing I wanted.

The widget code

import sys
import time
from PyQt5.QtCore import Qt, QRect
from PyQt5.QtWidgets import QApplication, QWidget, QCheckBox, QDesktopWidget

from PyQt5.QtGui import QPen, QPainter, QPaintEvent, QConicalGradient, QColor, QBrush

class Circle(QWidget):

    def __init__(self, size, color):
        super().__init__()

        self.loadingAngle = 0
        self.width = 0
        self.color = color
        self.pixmap_opacity = 1

        self.resize(size, size);
        self.center()

        self.initUI()

    def initUI(self):

        self.width = 15
        self.loadingAngle = 0
        self.show()

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def paintEvent(self, qevent):

        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setAttribute(Qt.WA_TranslucentBackground)
        self.setStyleSheet("background:transparent;")

        drawingRect = QRect()
        drawingRect.setX(qevent.rect().x() + self.width)
        drawingRect.setY(qevent.rect().y() + self.width)
        drawingRect.setWidth(qevent.rect().width() - self.width * 2)
        drawingRect.setHeight(qevent.rect().height() - self.width * 2)

        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)

        gradient = QConicalGradient()
        gradient.setCenter(drawingRect.center())
        gradient.setAngle(90)
        gradient.setColorAt(1, QColor(0,0,0))
        gradient.setColorAt(0, QColor(self.color[0], self.color[1],self.color[2]))

        arcLengthApproximation = self.width + self.width / 3
        pen = QPen(QBrush(gradient), self.width)
        pen.setCapStyle(Qt.RoundCap)
        painter.setPen(pen)
        painter.drawArc(drawingRect, 90 * 16 - arcLengthApproximation, -self.loadingAngle * 16)
        #time.sleep(0.25)

        if self.loadingAngle < 360:
            self.loadingAngle += 1
            #self.paintEvent(QDrawEvent())
            self.paintEvent(QPaintEvent())

The problematic line

self.paintEvent(QPaintEvent())

This line produces several errors, but even with them, I does what I want.

  1. If I pass the qevent from the function itself to this new call, the image doesn't get updated as I said before.

  2. If I create this new QPaintEvent, it does work. However, the errors are:

Traceback (most recent call last):

File "/home/btc/Escritorio/SinestesiaRCB/Clases/Widget.py", line 68, in paintEvent self.paintEvent(QPaintEvent())

TypeError: arguments did not match any overloaded call: QPaintEvent(QRegion): not enough arguments

QPaintEvent(QRect): not enough arguments

QPaintEvent(QPaintEvent): not enough arguments

QBackingStore::endPaint() called with active painter on backingstore paint device

These errors may be comming from the other lines like:

qevent.rect().x()

Since the new event is an empty one.

So basically my question is, how should I do it to make it correctly, meaning achieve what I want without errors?

PS. What I mean by progressively. This has been done creating several widgets, one second after the former each one.

enter image description here

1

1 Answers

1
votes

You should never call paintEvent directly, you must use update() to call it indirectly. On the other hand if you want to be called every so often you should use a QTimer or better a QTimeLine in this case.

import sys
from PyQt5 import QtCore, QtGui, QtWidgets

class Circle(QtWidgets.QWidget):
    def __init__(self, size, color):
        super().__init__()
        self._loading_angle = 0
        self.width = 0
        self.color = color
        self.pixmap_opacity = 1
        self.resize(size, size)
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
        self.setStyleSheet("background:transparent;")
        self.center()
        self.initUI()

        timeline = QtCore.QTimeLine(4000, self)
        timeline.setFrameRange(0, 360)
        timeline.frameChanged.connect(self.setLoadingAngle)
        timeline.start()

    def initUI(self):
        self.width = 15
        self.setLoadingAngle(0)
        self.show()

    def loadingAngle(self):
        return self._loading_angle

    def setLoadingAngle(self, angle):
        self._loading_angle = angle
        self.update()

    loadingAngle = QtCore.pyqtProperty(int, fget=loadingAngle, fset=setLoadingAngle)

    def center(self):
        qr = self.frameGeometry()
        cp = QtWidgets.QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def paintEvent(self, event):
        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.Antialiasing)
        drawingRect  = QtCore.QRect(QtCore.QPoint(), self.rect().size() - 2*self.width*QtCore.QSize(1, 1))
        drawingRect.moveCenter(self.rect().center())

        gradient = QtGui.QConicalGradient()
        gradient.setCenter(drawingRect.center())
        gradient.setAngle(90)
        gradient.setColorAt(1, QtGui.QColor(0,0,0))
        gradient.setColorAt(0, self.color)
        arcLengthApproximation = self.width + self.width / 3
        pen = QtGui.QPen(QtGui.QBrush(gradient), self.width)
        pen.setCapStyle(QtCore.Qt.RoundCap)
        painter.setPen(pen)
        painter.drawArc(drawingRect, 90 * 16 - arcLengthApproximation, -self._loading_angle * 16)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = Circle(400, QtGui.QColor("blue"))
    w.show()
    sys.exit(app.exec_())