Redrwaing a widget or window

Jeff Epler jepler at unpythonic.net
Wed Dec 25 22:38:22 EST 2002


On Thu, Dec 26, 2002 at 02:57:59AM +0000, John Smith wrote:
> I would like to draw an item on a canvas, wait a short period, draw another 
> item, etc.  So far my class never draws until the loop has run its full 
> course.  Any way to force a canvas, frame, or window to redraw?
> 
> John Smith
> -- 
> http://mail.python.org/mailman/listinfo/python-list

Yes.  The .update, .update_idletasks, .after, and .after_idle methods are
all possibly relevant to this.  

If you want to update the GUI and then go on to do more calculation,
call .update_idletasks or .update() (to also handle events).  If you want
to delay an approximately fixed length of time and then move, use .after()
with no function argument.  And if you want to act in an event-driven way,
use .after() with a callback that does the next step.

For instance, here are several ways to draw a line gliding across a canvas.  

The first method goes very quickly (there is no 'after' to impose a delay)
and doesn't respond to events (since only update_idletasks() is called).


In the second method, there's a >100ms (1/10 second) delay between updates.
Because update_idletasks() is called, the GUI is nonresponsive during the
~10 seconds it takes to sweep the line.  If this call were changed to
update() the GUI would be responsive, but this would also mean the user
could enter demo1() or another function recursively.  In this case both
incarnations of the function will use the same canvas 'c' and line 'l', so
if you press 'demo1' again halfway through, the line will do the full 100
pixel sweep (in the "inner" invocation of demo1) and then return to the
halfway point to finish the other 100 pixel sweep (in the "outer"
invocation of demo1).

In the third method, after(100, callback) is used.  The GUI remains
responsive, and there's no way to enter the routine "recursively".
However, there can still be two instances of the "demo2" class each being
called periodically.  This time, in their "quarrel", the after to be run
last "wins" and controls the position of the displayed line.

Note that if you are using a version which doesn't handle events, other
parts of your window will not redraw, though the parts of the canvas that
change will be updated.

If you are worried about the user invoking the same function twice, but
need the GUI to refresh if it is exposed, you must insert functionality
to disable the button before you do work that will service events.  This is
akin to a "race condition" in threaded code, and can be similarly hard to
trigger.  In this case, you'd do something like the following (untested):

    class Demo3:
	def __init__(self, b):
	    self.b = b

	def go(self):
	    self.i = 0
	    self.b.configure(state="disabled")
	    c.after(10, self.step)

	def step(self):
	    i = self.i = self.i + 1
	    c.coords(l, (i, 0, i, 100))
	    if i < 100:
		c.after(10, self.step)
	    else:
		self.b.configure(state="normal")
    b = Tkinter.Button(t, text="demo 3")
    b.pack()
    b.configure(command = Demo3(b).go)
    
Anyway, here's the code for the three basic approaches I described above:

------------------------------------------------------------------------
# sweep.py
# demo of different ways to animate a line on a canvas

import Tkinter

t = Tkinter.Tk()
c = Tkinter.Canvas(t)
c.pack()
l = c.create_line((0,0,0,100))  # All demos use the same canvas and line
Tkinter.Button(t, command=t.destroy, text="Close").pack()

def demo0():
    for i in range(100):
	c.coords(l, (i, 0, i, 100))
	c.update_idletasks()
Tkinter.Button(t, command=demo0, text="demo 0").pack()


def demo1():
    for i in range(100):
	c.coords(l, (i, 0, i, 100))
	c.update_idletasks()
	c.after(100)
Tkinter.Button(t, command=demo1, text="demo 1").pack()

class demo2:
    def __init__(self):
	self.i = 0
	c.after(10, self.step)

    def step(self):
	i = self.i = self.i + 1
	c.coords(l, (i, 0, i, 100))
	if i < 100:
	    c.after(10, self.step)
Tkinter.Button(t, command=demo2, text="demo 2").pack()

t.mainloop()
------------------------------------------------------------------------




More information about the Python-list mailing list