在 PySide/PyQt 中使用 drawPolyline 制作动画波浪 [英] Make an animated wave with drawPolyline in PySide/PyQt

查看:50
本文介绍了在 PySide/PyQt 中使用 drawPolyline 制作动画波浪的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试为多段线设置动画(它必须像波浪一样).我试过这种方式:

I'm trying to animate a polyline (it have to act like a wave). I've tried this way:

from PySide.QtCore import *
from PySide.QtGui import *
import sys, time

class Test(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)

    def poly(self, pts):
        return QPolygonF(map(lambda p: QPointF(*p), pts))

    def paintEvent(self, event):
        painter = QPainter(self)

        pts = [[80, 490], [180, 0], [280, 0], [430, 0], [580, 0], [680, 0], [780, 0]]

        for i in pts:
            while i[1] < 600:

                painter.setPen(QPen(QColor(Qt.darkGreen), 3))

                painter.drawPolyline(self.poly(pts))

                painter.setBrush(QBrush(QColor(255, 0, 0)))
                painter.setPen(QPen(QColor(Qt.black), 1))

                for x, y in pts:
                    painter.drawEllipse(QRectF(x - 4, y - 4, 8, 8))

                i[1] += 1
                print pts
                time.sleep(0.0025)
                self.update()

if __name__ == '__main__':
    example = QApplication(sys.argv)
    test2 = Test()
    test2.resize(800, 600)
    test2.show()
    sys.exit(example.exec_())

但是,它不起作用!程序运行时,屏幕上一团糟.看来, self.update() 不更新窗口.请帮忙.

But, it's not working! There is a mess on the screen, when the program runs. It seems, that self.update() doesn't update window. Please, help.

推荐答案

很明显,这段代码存在一些问题.我将列出我注意到的所有内容,然后进行解释:

There are a few issues with this code, obviously. I will list everything I notice, and then go through the explanations:

  1. 在paintEvent 中做太多处理
  2. 在那个paintEvent里面睡觉(不好)
  3. 在paintEvent内部调用self.update()

好的.绘制事件是小部件想要重绘的地方,应该尽可能快.你不应该在这个事件中做任何递归的事情,或者花太多时间,因为它会减慢你的抽奖速度.此外,在事件内部调用 update() 可能是递归的.绘制事件的目标应该是响应小部件的当前状态,绘制并退出.

Alright. A paint event is where the widget wants to redraw and should be as fast as possible. You should not be doing anything recursive in this event, or taking too much time as it will slow down your draw. Also, calling update() while inside your event is potentially recursive. The goal of the paint event should be to respond to the current state of the widget, paint, and get out.

这是您的代码的修改版本.这不是最理想的方法,但我会在下面解释更多...

Here is a modified version of your code that works. Its not the most ideal approach, but I will explain that more below...

from PySide.QtCore import *
from PySide.QtGui import *
import sys, time

class Test(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.pts = [[80, 490], [180, 0], [280, 0], [430, 0], [580, 0], [680, 0], [780, 0]] 

    def poly(self, pts):
        return QPolygonF(map(lambda p: QPointF(*p), pts))

    def paintEvent(self, event):
        painter = QPainter(self)

        pts = self.pts[:]

        painter.setPen(QPen(QColor(Qt.darkGreen), 3))
        painter.drawPolyline(self.poly(pts))

        painter.setBrush(QBrush(QColor(255, 0, 0)))
        painter.setPen(QPen(QColor(Qt.black), 1))

        for x, y in pts:
            painter.drawEllipse(QRectF(x - 4, y - 4, 8, 8))

        # print pts

    def wave(self):

        for point in self.pts:
            while point[1] < 600:
                point[1] += 1
                self.update()               
                QApplication.processEvents()
                time.sleep(0.0025)


if __name__ == '__main__':
    example = QApplication(sys.argv)
    test2 = Test()
    test2.resize(800, 600)
    test2.show()
    test2.raise_()
    test2.wave()
    sys.exit(example.exec_())

请注意,点已移至成员属性 self.pts,并且 paintEvent() 现在仅绘制点的当前状态.然后,动画逻辑被移动到另一个名为 wave() 的方法.在这个方法中,它循环修改每个点并调用update()来触发重绘.请注意,我们在paintEvent 之外调用了update().这很重要,因为如果您的应用程序中发生任何其他导致窗口重绘(调整大小等)的事件,您的paintEvent 可能会永远循环.

Notice that the points have been moved to a member attribute, self.pts, and the paintEvent() now only paints the current state of the points. Then, the animation logic is moved to another method called wave(). In this method, it loops and modifies each point and calls update() to trigger the redraw. Note we are calling update() outside of the paintEvent. This is important because should any other events occur in your application that cause the window to redraw (resizing, etc), you paintEvent could have looped forever.

所以我们修改了这个点列表,sleep,还有一个重要的添加它来调用QApplication.processEvents().通常,当应用程序空闲(离开当前调用)时处理事件.因为您不断调用重绘,并将这些事件堆叠起来,所以您需要告诉事件循环继续并刷新所有内容.尝试注释掉 processEvents() 命令,看看会发生什么.在循环完成之前,您的应用只会旋转什么都不做,并且生成的行将弹出到位.

So we modify this point list, sleep, and an important addition it to call QApplication.processEvents(). Normally, events are processed when the application becomes idle (leaves the current call). Because you are calling a repaint constantly, and stacking these events up, you need to tell the event loop to go ahead and flush everything through. Try commenting out that processEvents() command and see what happens. Your app would simply spin doing nothing until the loop is complete, and the resulting line will pop into place.

现在,对于我建议的部分,这并不是最理想的方法,尽管它可以作为示例.当前示例在执行 wave 时阻塞了主线程.您应该始终避免阻塞主线程,因为它纯粹是为了响应 GUI 事件.所以这里有一些可能的建议:

Now for the part where I was suggesting this isn't really the most ideal approach, though it works as an example. This current example blocks the main thread while it is performing a wave. You should always avoid blocking the main thread as its meant purely to respond to GUI events. So here are some possible suggestions:

  1. 您可以创建一个 QTimer使用 0.0025 动画速度作为超时.将 timeout() 信号连接到一个版本wave() 方法执行单个步骤并调用更新.这里不再需要睡觉了.一旦您的波浪计算结束,您将在 wave() 中检查它并调用 stop() 在计时器上.

  1. You could create a QTimer using the 0.0025 animation speed as a timeout. Connect the timeout() signal to a version of the wave() method that performs a single step and calls update. No sleep needed here anymore. Once your wave calculations have reached the end, you would check for that in wave() and call stop() on the timer.

将上述示例中的整个 wave() 循环和初始数据集移动到 QThread.这个 QThread 会一样发出自定义信号波更新(QPolygonF).当您启动此线程时,它会执行循环,并处理创建 QPolygonF 的操作,并且在每一步都会发出信号并进入睡眠状态.您可以将此信号连接到主窗口上的一个方法,该方法将接收新多边形,将其分配给 self._polygon,然后调用 update(),然后它会抓取 self._polygon 并绘制它.这里的想法是将尽可能多的繁重工作转移到线程中,并且只告诉您的主 GUI 线程使用新值重新绘制.

Move the entire wave() loop and initial dataset from the example above into a QThread. This QThread would emit a custom signal like waveUpdate(QPolygonF). When you start this thread it would do the loop, and handle creating the QPolygonF and on each step it would emit the signal and sleep. You could connect this signal to a method on your main window that would receive the new Polygon, assign it to self._polygon, and call update(), which would then just grab self._polygon and paint it. The idea here is to move as much of the heavy lifting as possible into the thread, and only tell your main GUI thread to repaint with new values.

这篇关于在 PySide/PyQt 中使用 drawPolyline 制作动画波浪的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆