使用pyqt5逐步绘制的正确方法

Posted

技术标签:

【中文标题】使用pyqt5逐步绘制的正确方法【英文标题】:Correct way to paint progressively with pyqt5 【发布时间】:2018-12-12 16:16:58 【问题描述】:

我有一个基本上是圆形的小部件。我想逐步绘制它,所以我需要逐步绘制它(imo)。

通过以下代码,我已经实现了我想要的。但是,有一个问题。我将 新事件 传递给 paintEvent 函数,因为如果我不这样做,图像在一切完成之前不会更新,所以我没有得到任何想要的结果。

小部件代码

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

这条线产生了几个错误,但即使有它们,我也会做我想做的。

    如果我将函数本身的 qevent 传递给这个新调用,图像不会像我之前所说的那样得到更新。

    如果我创建这个新的QPaintEvent,它确实有效。但是,错误是:

Traceback(最近一次通话最后一次):

文件“/home/btc/Escritorio/SinestesiaRCB/Clases/Widget.py”,第 68 行,在 paintEvent self.paintEvent(QPaintEvent())

TypeError:参数不匹配任何重载调用: QPaintEvent(QRegion): 没有足够的参数

QPaintEvent(QRect): 没有足够的参数

QPaintEvent(QPaintEvent): 没有足够的参数

QBackingStore::endPaint() 在 backingstore 绘制设备上使用活动绘制器调用

这些错误可能来自其他行,例如:

qevent.rect().x()

因为新事件是空的。

所以基本上我的问题是,我应该怎么做才能正确地做到这一点,这意味着没有错误地实现我想要的

PS。我所说的渐进式。这已经创建了几个小部件,每个小部件都在前一秒之后。

【问题讨论】:

【参考方案1】:

你不应该直接调用paintEvent,你必须使用update()来间接调用它。另一方面,如果您想经常被调用,那么在这种情况下,您应该使用 QTimer 或更好的 QTimeLine。

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

【讨论】:

以上是关于使用pyqt5逐步绘制的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

使用 Qt Designer 表单和 PyQt5 在 QWidget 中绘制 matplotlib 图形

在pyqt5中退出GUI时终止正在运行的进程的正确方法是什么?

PyQt5 QOpenGLWidget空闲问题

PyQt5 使用 keyPressEvent() 触发paintEvent()

如何在 PyQt5 中同时读取和写入文件时正确执行多线程?

PyQt5 将线条延伸到正在绘制的图像之外。我怎样才能防止这种情况发生?