Make an animated wave with drawPolyline in PySide/PyQt

2024/10/18 15:15:31

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, timeclass 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] += 1print ptstime.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.

Answer

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

  1. doing far too much processing in a paintEvent
  2. doing a sleep inside of that paintEvent (bad)
  3. calling self.update() while inside of a paintEvent

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, timeclass 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 ptsdef wave(self):for point in self.pts:while point[1] < 600:point[1] += 1self.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_())

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.

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:

  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.

  2. 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.

https://en.xdnf.cn/q/73142.html

Related Q&A

dateutil.tz package apparently missing when using Pandas?

My python 2.7 code is as follows:import pandas as pd from pandas import DataFrameDF_rando = DataFrame([1,2,3])...and then when I execute, I get a strange error regarding dateutil.tz./Library/Frameworks…

Importing SPSS dataset into Python

Is there any way to import SPSS dataset into Python, preferably NumPy recarray format? I have looked around but could not find any answer.Joon

Given a set of points defined in (X, Y, Z) coordinates, interpolate Z-value at arbitrary (X, Y)

Given a set of points in (X, Y, Z) coordinates that are points on a surface, I would like to be able to interpolate Z-values at arbitrary (X, Y) coordinates. Ive found some success using mlab.griddata …

Python multiprocessing speed

I wrote this bit of code to test out Pythons multiprocessing on my computer:from multiprocessing import Poolvar = range(5000000) def test_func(i):return i+1if __name__ == __main__:p = Pool()var = p.map…

Using os.forkpty() to create a pseudo-terminal to ssh to a remote server and communicate with it

Im trying to write a python script that can ssh into remote server and can execute simple commands like ls,cd from the python client. However, Im not able to read the output from the pseudo-terminal af…

Calculating the sum of a series?

This is my assignment and for the life of me i cant seem to think of a way to do it. This is the code I have so far:sum = 0 k = 1 while k <= 0.0001:if k % 2 == 1:sum = sum + 1.0/kelse:sum = sum - 1.…

get all the partitions of the set python with itertools

How to get all partitions of a set? For example, I have array [1, 2, 3]. I need to get [[1], [2], [3]], [[1], [2, 3]], [[2], [1,3]], [[3], [1, 2]], [[1, 2, 3]]. Now, I wrote this code:def neclusters(S…

Installing Python binary modules to a custom location in Windows

Suppose that I want to install a binary module for Python on Windows. Suppose that the module is distributed as a pre-built installer xxx-n.n.n.win32-py2.7.exe, prepared using distutils.My problem is t…

tkinter tkMessageBox html link

I got a tkMEssagebox.showerror showing up in a python tkinter application, when somebody failed to login with the application. Is it possible to have a url link in the tkMessageBox.showerror?ie.tkMess…

How to build a MultiIndex Pandas DataFrame from a nested dictionary with lists

I have the following dictionary.d= {key1: {sub-key1: [a,b,c,d,e]},key2: {sub-key2: [1,2,3,5,8,9,10]}}With the help of this post, I managed to successfully convert this dictionary to a DataFrame.df = pd…