Tkinter understanding mainloop
Question: Till now, I used to end my Tkiter programs with: `tk.mainloop()`, or nothing
would show up! See example:

    
    
    from Tkinter import *
    import random
    import time
    
    tk = Tk()
    tk.title = "Game"
    tk.resizable(0,0)
    tk.wm_attributes("-topmost", 1)
    
    canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0)
    canvas.pack()
    
    class Ball:
        def __init__(self, canvas, color):
            self.canvas = canvas
            self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
            self.canvas.move(self.id, 245, 100)
        def draw(self):
            pass
    
    ball = Ball(canvas, "red")
    
    tk.mainloop()
    

However, when tried the next step in this program(making the ball move by
time), the book am reading from, says to do the following. Change the draw
function to:

    
    
    def draw(self):
        self.canvas.move(self.id, 0, -1)
    

and add the following code to my program:

    
    
    while 1:
        ball.draw()
        tk.update_idletasks()
        tk.update()
        time.sleep(0.01)
    

But I noticed that adding this block of code, made the use of `tk.mainloop()`
useless, since everything would show up even without it!!!

_At this moment I should mention that my book never talks about`tk.mainloop()`
(maybe because it uses Python 3) but I learned about it searching the web
since my programs didn't work by copying book's code!_

So I tried doing the following that would not work!!!

    
    
    while 1:
        ball.draw()
        tk.mainloop()
        time.sleep(0.01)
    

What's going on? What does `tk.mainloop()`? What does `tk.update_idletasks()`
and `tk.update()` do and how that differs from `tk.mainloop()`? Should I use
the above loop?`tk.mainloop()`? or both in my programs?

Thanks!


Answer: `tk.mainloop()` _blocks_. What that means is that execution of your _python_
program halts there. You can see that by writing:

    
    
    while 1:
        ball.draw()
        tk.mainloop()
        print "hello"   #NEW CODE
        time.sleep(0.01)
    

You will never see the output from the print statement. Because there is no
loop, the ball doesn't move.

On the other hand, the methods `update_idletasks()` and `update()` here:

    
    
    while True:
        ball.draw()
        tk.update_idletasks()
        tk.update()
    

...do not block; execution continues on after those methods finish, so the
while loop executes over and over, which makes the ball move.

An infinite loop containing the method calls `update_idletasks()` and
`update()` can act as a substitute for calling `tk.mainloop()`. Note that the
whole while loop can be said to _block_ just like `tk.mainloop()` because
nothing after the while loop will execute.

However, `tk.mainloop()` is not a substitute for just the lines:

    
    
    tk.update_idletasks()
    tk.update()
    

Rather, `tk.mainloop()` is a substitute for the whole while loop:

    
    
    while True:
        tk.update_idletasks()
        tk.update()
    

**Response to comment:**

Here is what the [tcl docs](http://wiki.tcl.tk/13859) say:

> update idletasks
>
> This subcommand of update flushes all currently-scheduled idle events from
> Tcl's event queue. Idle events are used to postpone processing until “there
> is nothing else to do”, with the typical use case for them being Tk's
> redrawing and geometry recalculations. By postponing these until Tk is idle,
> expensive redraw operations are not done until everything from a cluster of
> events (e.g., button release, change of current window, etc.) are processed
> at the script level. This makes Tk seem much faster, but if you're in the
> middle of doing some long running processing, it can also mean that no idle
> events are processed for a long time. By calling update idletasks, redraws
> due to internal changes of state are processed immediately. (Redraws due to
> system events, e.g., being deiconified by the user, need a full update to be
> processed.)
>
> APN As described in Update considered harmful, use of update to handle
> redraws not handled by update idletasks has many issues. Joe English in a
> comp.lang.tcl posting describes an alternative:

So `update_idletasks()` causes some subset of events to be processed that
`update()` causes to be processed.

From the [update docs](http://wiki.tcl.tk/1252):

> update ?idletasks?
>
> The update command is used to bring the application “up to date” by entering
> the Tcl event loop repeatedly until all pending events (including idle
> callbacks) have been processed.
>
> If the idletasks keyword is specified as an argument to the command, then no
> new events or errors are processed; only idle callbacks are invoked. This
> causes operations that are normally deferred, such as display updates and
> window layout calculations, to be performed immediately.
>
> KBK (12 February 2000) -- My personal opinion is that the [update] command
> is not one of the best practices, and a programmer is well advised to avoid
> it. I have seldom if ever seen a use of [update] that could not be more
> effectively programmed by another means, generally appropriate use of event
> callbacks. By the way, this caution applies to all the Tcl commands (vwait
> and tkwait are the other common culprits) that enter the event loop
> recursively, with the exception of using a single [vwait] at global level to
> launch the event loop inside a shell that doesn't launch it automatically.
>
> The commonest purposes for which I've seen [update] recommended are: 1)
> Keeping the GUI alive while some long-running calculation is executing. See
> Countdown program for an alternative. 2) Waiting for a window to be
> configured before doing things like geometry management on it. The
> alternative is to bind on events such as that notify the process of a
> window's geometry. See Centering a window for an alternative.
>
> What's wrong with update? There are several answers. First, it tends to
> complicate the code of the surrounding GUI. If you work the exercises in the
> Countdown program, you'll get a feel for how much easier it can be when each
> event is processed on its own callback. Second, it's a source of insidious
> bugs. The general problem is that executing [update] has nearly
> unconstrained side effects; on return from [update], a script can easily
> discover that the rug has been pulled out from under it. There's further
> discussion of this phenomenon over at Update considered harmful.

.....

> Is there any chance I can make my program work without the while loop?

Yes, but things get a little tricky. You might think something like the
following would work:

    
    
    class Ball:
        def __init__(self, canvas, color):
            self.canvas = canvas
            self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
            self.canvas.move(self.id, 245, 100)
    
        def draw(self):
            while True:
               self.canvas.move(self.id, 0, -1)
    
    ball = Ball(canvas, "red")
    ball.draw()
    tk.mainloop()
    

The problem is that ball.draw() will cause execution to enter an infinite loop
in the draw() method, so tk.mainloop() will never execute, and your widgets
will never display. In gui programming, infinite loops have to be avoided at
all costs in order to keep the widgets responsive to user input, e.g. mouse
clicks.

So, the question is: how do you execute something over and over again without
actually creating an infinite loop? Tkinter has an answer for that problem: a
widget's `after()` method:

    
    
    from Tkinter import *
    import random
    import time
    
    tk = Tk()
    tk.title = "Game"
    tk.resizable(0,0)
    tk.wm_attributes("-topmost", 1)
    
    canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0)
    canvas.pack()
    
    class Ball:
        def __init__(self, canvas, color):
            self.canvas = canvas
            self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
            self.canvas.move(self.id, 245, 100)
    
        def draw(self):
            self.canvas.move(self.id, 0, -1)
            self.canvas.after(1, self.draw)  #(time_delay, method_to_execute)
    
    
    
    
    ball = Ball(canvas, "red")
    ball.draw()  #Changed per Bryan Oakley's comment
    tk.mainloop()
    

The after() method doesn't _block_ (it actually creates another thread of
execution), so execution continues on in your python program after after() is
called, which means tk.mainloop() executes next, so your widgets get
configured and displayed. The after() method also allows your widgets to
remain responsive to other user input. Try running the following program, and
then click your mouse on different spots on the canvas:

    
    
    from Tkinter import *
    import random
    import time
    
    root = Tk()
    root.title = "Game"
    root.resizable(0,0)
    root.wm_attributes("-topmost", 1)
    
    canvas = Canvas(root, width=500, height=400, bd=0, highlightthickness=0)
    canvas.pack()
    
    class Ball:
        def __init__(self, canvas, color):
            self.canvas = canvas
            self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
            self.canvas.move(self.id, 245, 100)
    
            self.canvas.bind("<Button-1>", self.canvas_onclick)
            self.text_id = self.canvas.create_text(300, 200, anchor='se')
            self.canvas.itemconfig(self.text_id, text='hello')
    
        def canvas_onclick(self, event):
            self.canvas.itemconfig(
                self.text_id, 
                text="You clicked at ({}, {})".format(event.x, event.y)
            )
    
        def draw(self):
            self.canvas.move(self.id, 0, -1)
            self.canvas.after(50, self.draw)
    
    
    
    
    ball = Ball(canvas, "red")
    ball.draw()  #Changed per Bryan Oakley's comment.
    root.mainloop()
    

