在PySide / PyQt中使用drawPolyline制作动画波形

时间:2012-02-27 12:29:25

标签: python pyqt pyqt4 pyside qpainter

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

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()没有更新窗口。 请帮忙。

1 个答案:

答案 0 :(得分:7)

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

  1. 在paintEvent中进行了太多的处理
  2. 在paintEvent(坏)内进行睡眠
  3. 在paintEvent内部调用self.update()
  4. 好的。绘制事件是窗口小部件想要重绘的位置,并且应该尽可能快。你不应该在这个事件中做任何递归,或者花太多时间,因为它会减慢你的平局。此外,在事件内部调用update()可能是递归的。 paint事件的目标应该是响应窗口小部件的当前状态,绘制和退出。

    以下是您的代码的修改版本。它不是最理想的方法,但我会在下面解释一下......

    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可能会永远循环。

    因此,我们修改此点列表,睡眠,并将其重要添加到QApplication.processEvents()。通常,在应用程序变为空闲时处理事件(离开当前调用)。因为你不断地调用重绘,并将这些事件堆叠起来,所以你需要告诉事件循环继续并将所有内容刷新。尝试评论processEvents()命令,看看会发生什么。

    ,你的应用程序只需旋转就可以完成任何操作,直到循环完成为止。

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

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

    2. 将上述示例中的整个wave()循环和初始数据集移动到QThread。这个QThread emit a custom signal就像waveUpdate(QPolygonF)一样。当你启动这个线程时,它将执行循环,并处理创建QPolygonF,并在每一步它将发出信号和睡眠。您可以将此信号连接到主窗口上将接收新Polygon的方法,将其分配给self._polygon,然后调用update(),然后只需抓取self._polygon并绘制它。这里的想法是将尽可能多的繁重工作移动到线程中,并且只告诉主GUI线程重新绘制新值。