Reputation: 2041
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.
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())
self.paintEvent(QPaintEvent())
This line produces several errors, but even with them, I does what I want.
If I pass the qevent
from the function itself to this new call, the image doesn't get updated as I said before.
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.
Upvotes: 2
Views: 2606
Reputation: 243907
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_())
Upvotes: 2