4

I have a simple code to visualise some data using tkinter. A button click is bound to the function that redraws the next "frame" of data. However, I'd like to have the option to redraw automatically with a certain frequency. I'm very green when it comes to GUI programming (I don't have to do a lot for this code), so most of my tkinter knowledge comes from following and modifying examples. I guess I can use root.after to achieve this, but I'm not quite sure I understand how from other codes. The basic structure of my program is as follows:

# class for simulation data
# --------------------------------

def Visualisation:

   def __init__(self, args):
       # sets up the object


   def update_canvas(self, Event):
       # draws the next frame

       canvas.delete(ALL)

       # draw some stuff
       canvas.create_........


# gui section
# ---------------------------------------

# initialise the visualisation object
vis = Visualisation(s, canvasWidth, canvasHeight)

# Tkinter initialisation
root = Tk()
canvas = Canvas(root, width = canvasWidth, height = canvasHeight)

# set mouse click to advance the simulation
canvas.grid(column=0, row=0, sticky=(N, W, E, S))
canvas.bind('<Button-1>', vis.update_canvas)

# run the main loop
root.mainloop()

Apologies for asking a question which I'm sure has an obvious and simple answer. Many thanks.

stuwilmur
  • 112
  • 1
  • 1
  • 7

2 Answers2

12

The basic pattern for doing animation or periodic tasks with Tkinter is to write a function that draws a single frame or performs a single task. Then, use something like this to call it at regular intervals:

def animate(self):
    self.draw_one_frame()
    self.after(100, self.animate)

Once you call this function once, it will continue to draw frames at a rate of ten per second -- once every 100 milliseconds. You can modify the code to check for a flag if you want to be able to stop the animation once it has started. For example:

def animate(self):
    if not self.should_stop:
        self.draw_one_frame()
        self.after(100, self.animate)

You would then have a button that, when clicked, sets self.should_stop to False

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Many thanks Bryan; I'd done something similar but had somehow one of my event bindings was messing things up. All working now. – stuwilmur Jul 17 '12 at 09:11
  • the first version causes the error maximum recursion depth reach if allowed to run indefinitely – rahul tyagi Jul 13 '15 at 14:36
  • 2
    @rahultyagi: no, it's impossible to exceed the maximum recursion depth because it's not a recursive function. At least, not in the literal sense. The function doesn't call itself, it merely schedules itself to run again in the future. The stack depth never exceeds 1. – Bryan Oakley Jul 13 '15 at 14:39
  • yeah i just read about that in another post totally got confused :) – rahul tyagi Jul 13 '15 at 14:49
  • @rahultyagi the stack depth would increase if you called the function by using parentheses (either empty or passing an argument). To solve that either lop off the parentheses or use a lambda function so the passed function is not called right away and not inside itself. – Alec White Feb 01 '18 at 20:58
  • 1
    @AlecKeyserWhite: _"the stack depth would increase if you called the function by using parentheses"_ - while true, this answer does not suggest calling the function with parenthesis so it doesn't have a problem with a growing stack. – Bryan Oakley Feb 01 '18 at 21:11
  • @BryanOakley Yes, I just wanted to follow up with Rahul on how to fix _if_ you need to pass arguments. You're solution works perfectly, but I added this clarification for others looking for help and encountering that error when adapting your solution to their needs. – Alec White Feb 01 '18 at 21:18
1

I just wanted to add Bryan's answer. I don't have enough rep to comment.

Another idea would be to use self.after_cancel() to stop the animation.

So...

def animate(self):
    self.draw_one_frame()
    self.stop_id = self.after(100, self.animate)

def cancel(self):
    self.after_cancel(self.stop_id)