【问题标题】:QPainter in pyqt5 is not painting anything when calling repaint() in a loop. How do I fix this?在循环中调用 repaint() 时,pyqt5 中的 QPainter 不会绘制任何内容。我该如何解决?
【发布时间】:2019-06-27 08:28:04
【问题描述】:

我对 PyQt 完全陌生。我想使用 PyQt5 做动画。这是我正在做的一个简单测试,所以我只是想将一个矩形从窗口的顶部移动到底部。以下是我为实现这一目标所做的工作的要点。

    1。我已经把我想画的东西放在了paintEvent() 方法中。我已经使用变量而不是常量值绘制了矩形
    2。我还创建了一个 update() 函数来更新所有变量
    3。我创建了一个循环函数,它每 100 毫秒调用一次 self.update() 和 self.repaint()
import sys
import random
from PyQt5.QtWidgets import ( QApplication, QWidget, QToolTip, QMainWindow)             
from PyQt5.QtGui import QPainter, QBrush, QPen, QColor, QFont
from PyQt5.QtCore import Qt, QDateTime

class rain_animation(QMainWindow):

    def __init__(self):
        super().__init__()

        self.painter = QPainter()

        """ Variables for the Window """
        self.x = 50
        self.y = 50
        self.width = 500
        self.height = 500

        """Variables for the rain"""
        self.rain_x = self.width/2
        self.rain_y = 0
        self.rain_width = 5
        self.rain_height = 30
        self.rain_vel_x = 0
        self.rain_vel_y = 5

        self.start()
        self.loop()

    def paintEvent(self, a0):

        self.painter.begin(self)

        # Draw a White Background
        self.painter.setPen(QPen(Qt.white, 5, Qt.SolidLine))
        self.painter.setBrush(QBrush(Qt.white, Qt.SolidPattern))
        self.painter.drawRect(0, 0, self.width, self.height)

        #Draw the rain
        self.painter.setPen(QPen(Qt.blue, 1, Qt.SolidLine))
        self.painter.setBrush(QBrush(Qt.blue, Qt.SolidPattern))
        self.painter.drawRect(self.rain_x, self.rain_y, self.rain_width, self.rain_height)

        self.painter.end(self)

    def update(self, diff):
        self.rain_x += self.rain_vel_x
        self.rain_y += self.rain_vel_y

    def start(self):
        self.setWindowTitle("Rain Animation")
        self.setGeometry(self.x, self.y, self.width, self.height)
        self.show()

    def loop(self):
        start = QDateTime.currentDateTime()
        while True :    
            diff = start.msecsTo(QDateTime.currentDateTime())
            if diff >= 100 :
                print("time : {0} ms rain_x : {1} rain_y : {2}".format(diff, self.rain_x, self.rain_y))
                start = QDateTime.currentDateTime()
                self.update(diff)
                self.repaint()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    animation = rain_animation()
    sys.exit(app.exec_())

我应该看到的是一个从窗口顶部移动到屏幕底部的矩形,但我看到的只是一个黑色背景的窗口。 loop() 函数似乎工作正常,因为我打印的数据显示变量每 100 毫秒更新一次。

虽然问题似乎出在 loop() 函数中,因为删除 self.loop() 后,我可以在窗口顶部看到带有白色背景的蓝色框的静态图片。

【问题讨论】:

    标签: python python-3.x animation pyqt pyqt5


    【解决方案1】:

    问题:

    具有连续循环不允许 GUI 执行诸如绘画、与操作系统交互等任务。每个 GUI 都提供了一种以不阻塞窗口的方式制作动画的方法。


    Qt 提供了各种类,允许您将动画实现为:

    • QTimer,
    • QTimeLine,
    • QVariantAnimation,
    • QPropertyAnimation。

    另一方面,建议:

    • 如果 QPainter 将负责 GUI 绘制,请不要在 paintEvent 之外创建它。
    • 使用 update() 方法(不是你的方法,而是提供 Qt 的方法)而不是 repaint,以防 repaint 有时会强制窗口不必要地绘制,而不是 update() 会在必要时执行,记住绘制的以屏幕的刷新率 (60 Hz) 完成。例如,如果您在 20 毫秒内调用 repaint 5 次,则paintEvent() 将被调用 3 次,但屏幕上的绘制是每 16.6 毫秒一次,因此您只需要 1 次绘制,如果您考虑使用 update() 的话。

    考虑到上述情况,最好使用QPropertyAnimation:

    import sys
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class RainAnimation(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
            self.setWindowTitle("Rain Animation")
            self.setGeometry(50, 50, 500, 500)
            self.m_rect_rain = QtCore.QRect()
    
            animation = QtCore.QPropertyAnimation(
                self,
                b"rect_rain",
                parent=self,
                startValue=QtCore.QRect(self.width() / 2, 0, 5, 30),
                endValue=QtCore.QRect(self.width() / 2, self.height() - 30, 5, 30),
                duration=5 * 1000,
            )
    
            animation.start()
    
        def paintEvent(self, a0):
            painter = QtGui.QPainter(self)
            # Draw a White Background
            painter.setPen(QtGui.QPen(QtCore.Qt.white, 5, QtCore.Qt.SolidLine))
            painter.setBrush(QtGui.QBrush(QtCore.Qt.white, QtCore.Qt.SolidPattern))
            painter.drawRect(self.rect())
            #Draw the rain
            painter.setPen(QtGui.QPen(QtCore.Qt.blue, 1, QtCore.Qt.SolidLine))
            painter.setBrush(QtGui.QBrush(QtCore.Qt.blue, QtCore.Qt.SolidPattern))
            painter.drawRect(self.rect_rain)
    
        @QtCore.pyqtProperty(QtCore.QRect)
        def rect_rain(self):
            return self.m_rect_rain
    
        @rect_rain.setter
        def rect_rain(self, r):
            self.m_rect_rain = r
            self.update()
    
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        w = RainAnimation()
        w.show()
        sys.exit(app.exec_())
    

    另一种选择是使用 QVarianAnimation:

    import sys
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class RainAnimation(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
            self.setWindowTitle("Rain Animation")
            self.setGeometry(50, 50, 500, 500)
            self.m_rect_rain = QtCore.QRect()
    
            animation = QtCore.QVariantAnimation(
                parent=self,
                startValue=QtCore.QRect(self.width() / 2, 0, 5, 30),
                endValue=QtCore.QRect(self.width() / 2, self.height() - 30, 5, 30),
                duration=5 * 1000,
                valueChanged=self.set_rect_rain,
            )
            animation.start()
    
        def paintEvent(self, a0):
            painter = QtGui.QPainter(self)
            # Draw a White Background
            painter.setPen(QtGui.QPen(QtCore.Qt.white, 5, QtCore.Qt.SolidLine))
            painter.setBrush(QtGui.QBrush(QtCore.Qt.white, QtCore.Qt.SolidPattern))
            painter.drawRect(self.rect())
            # Draw the rain
            painter.setPen(QtGui.QPen(QtCore.Qt.blue, 1, QtCore.Qt.SolidLine))
            painter.setBrush(QtGui.QBrush(QtCore.Qt.blue, QtCore.Qt.SolidPattern))
            painter.drawRect(self.m_rect_rain)
    
        @QtCore.pyqtSlot(QtCore.QVariant)
        def set_rect_rain(self, r):
            self.m_rect_rain = r
            self.update()
    
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        w = RainAnimation()
        w.show()
        sys.exit(app.exec_())
    

    以下示例使用您的逻辑,但使用 QTimer:

    import sys
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class RainAnimation(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
            self.setWindowTitle("Rain Animation")
            self.setGeometry(50, 50, 500, 500)
            self.m_rect_rain = QtCore.QRect(self.width() / 2, 0, 5, 30)
    
            timer = QtCore.QTimer(self, timeout=self.update_rain, interval=100)
            timer.start()
    
        def paintEvent(self, a0):
            painter = QtGui.QPainter(self)
            # Draw a White Background
            painter.setPen(QtGui.QPen(QtCore.Qt.white, 5, QtCore.Qt.SolidLine))
            painter.setBrush(QtGui.QBrush(QtCore.Qt.white, QtCore.Qt.SolidPattern))
            painter.drawRect(self.rect())
            # Draw the rain
            painter.setPen(QtGui.QPen(QtCore.Qt.blue, 1, QtCore.Qt.SolidLine))
            painter.setBrush(QtGui.QBrush(QtCore.Qt.blue, QtCore.Qt.SolidPattern))
            painter.drawRect(self.m_rect_rain)
    
        @QtCore.pyqtSlot()
        def update_rain(self):
            self.m_rect_rain.moveTop(self.m_rect_rain.top() + 5)
            self.update()
    
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        w = RainAnimation()
        w.show()
        sys.exit(app.exec_())
    

    【讨论】:

    • 感谢完美!我使用了 QTimer 方法,因为它似乎比其他方法更灵活
    • @SaritDas 在我的情况下,我更喜欢 QPropertyAnimation,因为运动更平滑和优化
    • 我想知道如果 update_rain() 中的计算时间超过 100 毫秒会发生什么
    • 我想用这些 pyqt 特性完全从头开始构建游戏、具有碰撞检测、光照和阴影特性的 3D 图形引擎。我那些情况下的计算有时会花费很多时间。这就是为什么我想知道如果 update_rain() 函数中的计算花费的时间超过了间隔 = 时间部分中指定的时间会发生什么
    • @SaritDas 你会看到一个元素如何从一个位置跳到另一个位置,移动会很突然。 TL; DR:我建议不要从头开始创建它,因为您可能没有得到任何好处,Qt 已经提供了一个 3d 模块:doc.qt.io/qt-5/qt3d-index.html,该模块已经使用 GPU 优化了某些任务。正如您所说,您似乎计划用 CPU 实现所有内容,因此您将遇到许多不良问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-25
    • 2019-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多