Tkinter Understanding Mainloop

Tkinter understanding mainloop

tk.mainloop() blocks. It means that execution of your Python commands 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; after those methods finish, execution will continue, so the while loop will execute 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 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:

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()

Understanding mainloop() better

The mainloop() method runs the event processing loop on the current thread until all windows started in the thread have closed or the quit() method is called (which internally sets a flag that is checked by the core event loop runner). It blocks the thread, except that it does all the registered callbacks as necessary. The underlying machinery is not actually associated with any particular window, but rather with a thread. It's just that Tkinter prefers to not map things as free functions, despite that really being what they are.

You're pretty strongly recommended to not have the results of two calls to tkinter.Tk() active at once. It works, but the result can be quite confusing. It can get even more confusing if you do it from several threads at once (which is supposed to work — except on macOS for messy reasons — but the results tend to be mind-bending). (This does not apply to tkinter.Tcl(); that does work reasonably when done multiple times, and from multiple threads. The object it produces is thread-bound, but having many instances is sensible, and you can have those instances either in their own threads or together.)


What doing two calls to tkinter.Tk() like you've done does is create two separate underlying Tcl/Tk environments (the technical term is an “interpreter” even though that's got a bytecode compiler and so on) sharing a thread. This is sensible from a Tcl/Tk perspective due to the very different security model that Tcl uses, but it's almost total nonsense from the view of Python. The effects are describable small-scale operationally, but making broader sense of them is very difficult, and you're advised to not try.

When do I need to call mainloop in a Tkinter application?

The answer to your main question is, you must call mainloop once and only once, when you are ready for your application to run.

mainloop is not much more than an infinite loop that looks roughly like this (those aren't the actual names of the methods, the names merely serve to illustrate the point):

while True:
event=wait_for_event()
event.process()
if main_window_has_been_destroyed():
break

In this context, "event" means both the user interactions (mouse clicks, key presses, etc) and requests from the toolkit or the OS/window manager to draw or redraw a widget. If that loop isn't running, the events don't get processed. If the events don't get processed, nothing will appear on the screen and your program will likely exit unless you have your own infinite loop running.

So, why don't you need to call this interactively? That's just a convenience, because otherwise it would be impossible to enter any commands once you call mainloop since mainloop runs until the main window is destroyed.

Is tkinter's mainloop() function actually a loop?

I am confused about the mainloop(). What exactly is looping?

Tkinter maintains a queue of events. mainloop loops over that queue, pulling items off and executing functions bound to the events.

If this program was looping through class Test ...

It doesn't loop through your code. There is an internal, constantly updating list of events. mainloop loops over that list. It doesn't loop over your code.

Python3, Understanding the GUI loop as in tkinter-mainloop() or QtPy-exec_()

[1] GUIs are polling based?

GUIs are event based -- everything that happens in a GUI is a response to an event. How the application handles the event is up to the application. If the handling of the event can happen in a few hundred milliseconds or less it can be handled in the main thread. If it is going to take longer, the application should run the code in a separate thread or process.

In the case of Tkinter, Tkinter is designed to run in a single thread. That doesn't mean you can't use threads, only that all access to tkinter objects should be on a single thread. The common way to do this is for other threads to communicate with the GUI via a thread-safe queue, and the GUI thread is responsible for checking that queue periodically.

So my question is, Is there an option like [root.setInterruptMode(True)]

In tkinter, no, there is no way to enter a special "interrupt" mode.

[2] It is just a gigantic loop?

I assume by "it" you mean the call to mainloop. Yes, it's a gigantic loop. Or perhaps more correctly, it's a tiny loop. All it does is wait for an event, then it looks up the handler for the event and runs the handler.

My impression is that mainloop() doesn't really start a thread or anything in python

Correct. It runs the event loop in the current thread.

[3] why putting mainloop in Main line?

You would have to ask the original developers that. Likely it's because that's all that is necessary for a very, very large percentage of applications (perhaps more true back when the toolkit was invented than it is now).

Tkinter is nothing but a wrapper around a tcl interpreter with the tk toolkit loaded in the interpreter. tcl/tk was designed to be embedded in other applications, so it needed to be lightweight, and be able to run on systems that don't support threads (tcl is old enough that thread support wasn't guaranteed on every platform)

Does mainloop() have to reside in the Main line? can I thread/multiprocess it out?

mainloop() needs to be run in the same thread that created the root window. If you need threads (and again, it's quite possible that you don't), you can certainly run your event handlers in a separate thread. It adds complexity, but it's there if you need it. For a large class of programs, there's simply no need for that much complexity.

You should be able to create a worker thread and create and run your GUI from there. I've never tried it, but I see no reason why it wouldn't work. Tkinter doesn't care that it's the main thread, only that all tkinter code runs in the same thread.

All the examples I came across have mainloop() in the Main line, I am not sure it is a recommended approach or what the benefits are.

Calling mainloop in the main thread is how tkinter was designed to work. If you have long running calculations, you should put those calculations in a separate thread or process.

Tkinter Mainloop Unreachable

The mainloop is executed after while loop end Which is never end this is because you are not seeing the window here.

If you want to run this code then you can use the root.after() method to create a loop.

from tkinter import *
root = Tk()
root.geometry('400x400')

def testlen():
global textinentry1
textinentry1= entry1.get()
if len(textinentry1) >1 :
entry2.delete(0, END)
entry2.insert(0,textinentry1[1] )



entry1 = Entry(root,width=22, font = ('Georgia 18'), justify=CENTER)
entry1.grid(row=0, column=0)
entry2 = Entry(root, width=22, font = ('Georgia 18'), justify=CENTER)
entry1button = Button(root, text="Enter", command = lambda :testlen())
entry1button.grid(row=1, column=0)
entry2.grid(row=2, column=0)
entry3 = Entry(root, width = 22,font = ('Georgia 18'), justify=CENTER)
entry3.grid(row=4, column=0)
def loop():
testlen()
root.after(1,loop) # 1 is 1 millisecond. Here root.after method call the loop function after 1 millisecond without crashing your code.
loop()

root.mainloop()


Related Topics



Leave a reply



Submit