wxPython fast and slow

Carl Banks pavlovevidence at gmail.com
Mon Mar 9 03:56:31 CET 2009


On Mar 8, 12:35 pm, iu2 <isra... at elbit.co.il> wrote:
> On Mar 8, 1:42 pm, Carl Banks <pavlovevide... at gmail.com> wrote:
>
>
>
> > On Mar 8, 1:52 am, iu2 <isra... at elbit.co.il> wrote:
>
> > > On Mar 6, 6:52 pm, Mike Driscoll <kyoso... at gmail.com> wrote:
>
> > > > ...
> > > > Can you post a sample application so we can try to figure out what's
> > > > wrong? You might also cross-post this to thewxPythonmailing list.
> > > > They might know.
>
> > > > Mike- Hide quoted text -
>
> > > > - Show quoted text -
>
> > > Hi, thanks for your reply
>
> > > Here is a sample application:
>
> > > ----------------------------------------------------------
>
> > > import wx
> > > import time
>
> > > class My_frame(wx.Frame):
> > >     def __init__(self):
> > >         wx.Frame.__init__(self, None, -1, 'Moving panel')
> > >         self.surface = p = wx.Panel(self, size=(300, 130))
> > >         self.square = wx.Panel(p, -1, size=(100, 100), pos=(0, 30))
> > >         self.square.Bind(wx.EVT_PAINT, self.on_paint_square)
>
> > >         btn_move = wx.Button(p, -1, 'Move panel', pos=(0, 0))
> > >         self.Bind(wx.EVT_BUTTON, self.move_panel, btn_move)
>
> > >         self.Fit()
>
> > >     def move_panel(self, evt):
> > >         def gen():
> > >             for x in range(200):
> > >                 yield x
> > >             for x in range(200, 0, -1):
> > >                 yield x
> > >         for x in gen():
> > >             self.square.SetPosition((x, 30))
> > >             time.sleep(0.005)
>
> > >     def on_paint_square(self, evt):
> > >         square = evt.GetEventObject()
> > >         dc = wx.BufferedPaintDC(square)
> > >         dc.Pen = wx.Pen('blakc', 2)
> > >         dc.Brush = wx.Brush('light blue')
> > >         dc.DrawRectangle(0, 0, *square.GetSize())
>
> > > app = wx.PySimpleApp()
> > > My_frame().Show()
> > > app.MainLoop()
>
> > > ----------------------------------------------------------
>
> > > Press the button and the panel moves to the right and then back to the
> > > left.
> > > While PyScripter is running the panel moves at a certain speed. You
> > > can run the application from the Windows explorer with the same speed.
> > > You don't need to run it from PyScripter.
> > > When PyScripter is closed, the application runs much less quickly.
> > > I experienced this on two PC-s.
> > > Maybe this behavior is not even related towxPythonbut to the sleep
> > > command. I don't know...
>
> > It's not a good idea to call time.sleep inside a loop inside an event
> > handler, which is what you are doing here.
>
> > wx has a mechanism to call some sort of callback at timed intervals.
> > (I don't know what it is but I know it has one.)  Instead of animating
> > the box move inside the button callback, have it request that a
> > callback be called after x seconds pass, and draw a single frame
> > inside that callback.  Then invoke it again until you're done drawing.
>
> > Here's a rough idea of what that might look like:
>
> > class My_frame(wx.Frame):
>
> >     # ... among other things ...
>
> >     def move_panel(self,evt):
> >         self.square_pos = 0
> >         wx.set_timed_callback_of_some_sort(
> >             self.draw_frame,0.005)
>
> >     def draw_frame(self):
> >         self.square.SetPosition((self.square_pos, 30))
> >         self.square_pos += 1
> >         if self.square_pos < 200:
> >             wx.set_timed_callback_of_some_sort(
> >                self.draw_frame,0.005)
>
> > As for why it works fine in PyScripter but not when started from
> > Explorer, well, let's just say that when you abuse callbacks like you
> > did, you shouldn't expect reasonable behavior.
>
> > Carl Banks
>
> Hi,
>
> Here is the timer version. It works even more slowly, even with
> PyScripter active:
>
> --------------------------------------------------------------
> import wx
> import time
>
> class My_frame(wx.Frame):
>     def __init__(self):
>         wx.Frame.__init__(self, None, -1, 'Moving panel')
>         self.surface = p = wx.Panel(self, size=(300, 130))
>         self.square = wx.Panel(p, -1, size=(100, 100), pos=(0, 30))
>         self.square.Bind(wx.EVT_PAINT, self.on_paint_square)
>
>         btn_move = wx.Button(p, -1, 'Move panel', pos=(0, 0))
>         self.Bind(wx.EVT_BUTTON, self.move_panel, btn_move)
>
>         self.Fit()
>
>     def on_paint_square(self, evt):
>         square = evt.GetEventObject()
>         dc = wx.BufferedPaintDC(square)
>         dc.Pen = wx.Pen('blakc', 2)
>         dc.Brush = wx.Brush('light blue')
>         dc.DrawRectangle(0, 0, *square.GetSize())
>
>     def move_panel(self, evt):
>         def gen():
>             for x in range(200):
>                 yield x
>             for x in range(200, 0, -1):
>                 yield x
>         self.track = gen()
>         self.timer = wx.Timer(self)
>         self.Bind(wx.EVT_TIMER, self.on_timer_square, self.timer)
>         self.timer.Start(milliseconds=1)
>
>     def on_timer_square(self, evt):
>         try:
>             x = self.track.next()
>             self.square.SetPosition((x, 30))
>         except StopIteration:
>             self.timer.Stop()
>
> app = wx.PySimpleApp()
> My_frame().Show()
> app.MainLoop()
> -----------------------------------------------------------------
>
> I actually tried this one first. Due to the slow speed I changed to
> looping inside the event.
> I don't understand why it takes so long to move that square with
> wx.Timer set to 1 ms interval. Perhaps its minimum interval is
> actually 10 ms (as in Windows) so 100 steps really take 1 second.
> But in that case, I really want to move the square in a tight loop
> inside the wx.EVT_BUTTON event.

No, you don't *ever* want to do this.  GUI frameworks are designed for
event handlers to execute quickly.  It won't work reliably to hijack
an event handler.  It might work ok sometimes, but other times it'll
freeze the application while it's executing, perhaps even the whole
windowing system.  Some toolkits might even queue the graphics
commands and not draw anything until the event handler returns.  Just
don't do it.


> So allow me to rephrase my question:
> Is there a way to move that square quickly and smoothly? Should 400
> one-pixel moves should take so long on a 2.8 GHz core duo?

Not really.  The issue isn't core speed; other factors limit the
maximum output rate.

Your monitor probably has a maximum update rate of 60-80 Hz range,
which means regardless of how fast your CPU is, you can't see updates
faster than this.  Which means that it'd be impossible to move your
square to move one pixel at time 100 pixels and finish in 1 second and
see every frame.

There are other things in the system that contribute to it, too.  The
real issue is that a typical desktop system isn't designed to push
very high frame rates.  Your system might be able to draw 1000 squares
50 times a second, but it doesn't mean it can draw 500 squares at 100
frames per second, or even one square at that speed.


> There is certainly something wrong in the code I wrote which I need
> your help to figure out.
> Can it be related to recurring paint events? If so how should I change
> the code?

What's wrong, I think, is your understanding of "smooth" animation.
It's not very important to use small increments of space, only small
increments of time.

In other words, you don't have to draw that square in 1 pixel
increments for it to look smooth, you only have to draw it at at a
reasonably fast frame rate (say 25 Hz for starters), and draw it where
it's supposed to be at the right time.

I recommend you write a function that calculates the square's position
as a function of time, and whenever you get a timer event, calculate
the elapsed time, figure out where the square should be and draw it
there.  Something like this:


    def calc_position(t):
        if t < 0.2:
            x = t/1000
        elif t < 0.4:
            x = 200 - (t-0.2)/1000
        else:
            raise ValueError('animation over')

    def move_panel(self,evt):
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.on_timer_square,
                  self.timer)
        self.timer.Start(milliseconds=40)  # 25 Hz

    def on_timer_square(self, evt):
        try:
            # I'm not sure how to get elapsed time since timer is
started
            x = self.calc_position(self.timer._GetElapsedTime())
        except ValueError:
            self.timer.Stop()
        else:
            self.square.SetPosition((x, 30))


(For very advanced graphics, small increments in space do come into
play for fast-moving objects; however, such cases are usually handled
by blurring the object in question.  For instance they'd draw the
square to a memory buffer 5 times, then average them to get the
blurred version, and draw that.)



Carl Banks



More information about the Python-list mailing list