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_())
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:
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...
Notice that the points have been moved to a member attribute,
self.pts
, and thepaintEvent()
now only paints the current state of the points. Then, the animation logic is moved to another method calledwave()
. In this method, it loops and modifies each point and callsupdate()
to trigger the redraw. Note we are callingupdate()
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.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.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:
You could create a QTimer using the
0.0025
animation speed as a timeout. Connect the timeout() signal to a version of thewave()
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 inwave()
and call stop() on the timer.Move the entire
wave()
loop and initial dataset from the example above into a QThread. This QThread would emit a custom signal likewaveUpdate(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 toself._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.