Tkinter code runs without mainloop, update, or after - python

I have been trying to use tkinter to make a gui to select some excel files and sheets from those files.
I have a lot of experience in python, but I'd probably say a novice at tkinter.
The code I have written to select the files is shown below (typos are likely because I cannot access the internet on the machine these files are on, so I am typing it up here).
My question is about mainloop(), the update functions, and after(). I had mainloop() at the end of my code, but my program wouldn't terminate (i.e. the terminal would remain) after it did what it does. So I removed the mainloop() and now it functions perfectly without any mainloop(), update(), or after() calls.
I don't really understand this and would really like to. I get that mainloop() stops the code from progressing until the root closes, but I thought nothing would show up without mainloop(), and this code does wait for the user to close the windows before continuing.
I was also wondering if I do not have mainloop (or the like), the code still closes fine whether or not I have root.destroy() at the end of the App class (commented in the code). I don't get that either.
This information would help me make better and correct code in the future and hopefully improve this one. FYI, I have searched for the answer to this everywhere and could not find one, at least one I could understand. Thanks a bunch.
This is as minimal as I can think of for this code.
Code (Edited from original post):
import tkinter as tk
from tkinter import ttk
class App:
def __init__(self, parent):
self.root = parent
self.root.withdraw()
tl = tk.Toplevel(parent)
b = ttk.Button(tl, text="Test widget")
b.grid()
tl.wait_window()
# This line does not change how the code functions at all
#self.root.destroy()
def run(self):
# Whether or not this is a function in the class or called globally doesn't matter.
self.root.mainloop()
if __name__ == "__main__":
root = tk.Tk()
app = App(root)
# This is the mainloop() call. If I include it, the program does not close after
# running the code in App.__init__. Without it, it runs perfectly.
# app.run()

mainloop enters an event listening loop for your tk.Tk() application.
You should create only one object with Tk() in every tkinter application.
Now this loop will "globally" listen for GUI events and will call your registered callbacks (event handlers). Code before the mainloop call will run but nothing will be shown or updated in the screen until you call it. No event (user input, mouse movement, keyboard and others) will be queued and responded to before that. But the widgets are created and configured, just not shown yet. If you call root.update() without ever entering the mainloop you will eventually see the window flash and disappear because everything is running, and you force window refresh, but then suddenly the program ends.
So configure your GUI and in the end always run root.mainloop()
Now when you call wait_window, you are creating a local event loop (and blocking the mainloop if it was running). For a perfect explanation of wait_window by Brian see here
What happened to you is that your app is happily running with your local event loop, but without mainloop. That would be a strange thing to do. Even if you are making as Brian explains, a modal window, you want to "return" to the mainloop after the window closes.
As for the after method it just registers a callback function to run after your choosen time, or after the mainloop becames idle, with after_idle (nothing more to refresh, no events in the queue..., also think as soon as possible). And if you want to re-run that function again in the same way it should re-register with after or after_idle before returning.
So always use your mainloop() ending call - or should I say event loop starting call :)
Edit:
Also, don't do things in your code that are slow, or that may block the loops, such as calling sleep or any other blocking function. That will freeze the GUI, until the block ends.

Related

how to break out of mainloop() without closing the gui

I have a simple python GUI that fills in a grid based on the values in an array, and I want this simple GUI to sit on screen while other functions run. The time for these other functions is indeterminate, so I can't simply call them at some regular interval using after(time, function)
def go(self):
"""
Start the main GUI loop
:return:
"""
mainloop()
The mainloop starts in the go function, and I want to know if it is possible to either break out of mainloop without closing the window or to draw the GUI without calling mainloop at all?
I know I can do
after(time, function)
but I'm at a loss as to what to call to kill the mainloop while keeping the GUI on-screen.
Use the threading module to run your function alongside the mainloop:
from threading import Thread
def go(self):
t = Thread(target=function)
t.start() # start the thread in the background
mainloop()
Simply put: you can't, or at least it's not what you want to do. I would recommend restructuring you code to put it in the main loop (like making tkinter a class and calling the functions in your init function). If you post your whole code I can help you with it.
The mainloop starts in the go function, and I want to know if it is possible to either break out of mainloop without closing the window or to draw the GUI without calling mainloop at all?
It sounds like you have a very unique need. To give you a literal answer, yes, it's possible to break out of mainloop without closing the window. You can call quit on the root window to cause mainloop to exit. You can restart mainloop, or call the update method on the root window to process events.
This is not typically how a GUI should work, but if you're absolutely certain that you don't need the window to update while the program is running, you can certainly do that if you wish.

tkinter.mainloop does not work

I am trying to create a basic Tkinter window.
According to on-line tutorials, to create a window one must use the following:
import Tkinter
window=Tkinter.Tk()
window.mainloop()
But when I try the same code python directly displays the window in window=Tkinter.Tk() and window.mainloop() has no effect.
Can anyone explain why ?
EDIT: The code works perfectly when I put it in a file and run it. It just doesn't work from interactive prompt.
The call to mainloop is there so that you can interact with the Window once it's created. If you had a Python script that only did this:
import Tkinter
window = Tkinter.Tk()
The script would exit immediately after window was created, so you'd be luckily to even see it get drawn before it disappeared as the script exited. (That is if window was even drawn at all; in my tests on both Linux and Windows, window was never drawn unless mainloop was called; even if I put a call to time.sleep after the Tkinter.Tk() call, window would only be drawn without a mainloop call in the interactive prompt).
The mainloop() also (and most importantly) allows Tkinter to listen for events to occur on the Tk object, such as pressing buttons, radios, etc. that might be embedded in it, and dispatch those events to methods you have bound to the event being triggered. Without that functionality you'd just have a window that you can look at and not much else.

Tkinter method deiconify seems not working on ubuntu(12.04, unity)

Tkinter method "deiconify" seems not working on ubuntu(12.04, unity), but the the following code works as expected on windows 7. I need to show the window even it is minimized when something happens in another project https://github.com/thinker3/youdao.
from time import sleep
from Tkinter import Tk
class GUI():
def __init__(self):
self.root = Tk()
self.root.title("Test")
self.root.protocol("WM_DELETE_WINDOW", self.close_handler)
self.root.mainloop()
def close_handler(self):
self.root.iconify()
sleep(1)
self.root.deiconify()
if __name__ == '__main__':
gui = GUI()
What you are seeing is apparently the difference between Windows and non-Windows. It looks to me like the Windows behavior is a bug. At the very least, it's not expected behavior. What you are seeing on ubuntu is what I would expect to see.
For a GUI to do anything, the event loop must be running. Everything that happens is the response to an event. The drawing of a window on the screen is a response to an event. A button click is a response to an event. The visual effect of a button being pressed is a response to an event. And so on.
When you call iconify, that causes an event to be sent to the app saying "remove the window from the screen". The event loop sees the event, and redraws (or "un"draws) the window. The reverse happens when you call deiconify -- the system gets a redraw event, and tkinter redraws the window on the screen.
In your code, you never give the event loop a chance to respond to these events. You ask it to iconfiy, then you sleep, then you deiconify, all without giving the event loop a chance to respond. While the sleep command is running no events are processed. So, when you wake from sleep, the system hides the window, and then microseconds later it redraws it.
What is probably happening on windows is that the window manager is getting the iconfiy command and removing the window without tkinter's involvement. In other words, tkinter doesn't actually do anything to make it go away. On X11-based systems, however, this is all managed by the event loop.
If you want the window to go away, and a second later reappear, use the event loop to your advantage. Allow the event loop to update the screen, and arrange for the deiconify to happen at sometime in the future. For example:
def close_handler(self):
self.root.iconify()
self.after(1000, self.root.deiconify)
This should work on all platforms. It allows the event loop to respond to the iconify event, and then a second later the deiconify command will run.

lift() method of python's Tkinter package not working

I am trying to build an application with a main window, and a background thread that checks a certain condition in a loop, and when the condition is met, brings the main window to the front.
To bring the window to the front i use the lift() method, but it doesn't work.
So I did the following experiment from the python command line:
from Tkinter import *
root = Tk()
A tk window appears. Minimize the window and then:
root.lift()
Nothing happens. I also tried start the window's mainloop before lifting:
import thread
# start mainloop in separate thread so we can continue typing
thread.start_new_thread(root.mainloop, ())
root.lift()
Again, nothing happens. In my actual code it is even worse - once I call lift(), the window is stuck and stops responding.
What am I doing wrong?
(I'm using Python 2.7.2 on Windows 7.)
You cannot run the mainloop in a thread different from the one where you create the widgets. In fact, you can't interact with widgets at all from another thread.
To revert the effects of minimizing the window you need to use the deiconify method.

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

Every tkinter tutorial I have seen claims that tkinter.mainloop must be called for windows to be drawn and events to be processed, and they always call this function, even in hello world programs. However, when I try these out in the interactive shell, windows are drawn correctly without having to call mainloop. This example of embedding matplotlib graphics in tkinter produces a relatively complex application, with buttons for panning, zooming and resizing a plot within a tkinter window, and again, this all works if you remove the call to mainloop and run the code in the interactive shell. Of course, if I run the script (with mainloop removed) outside the interactive shell, the program ends too quickly to see what happens, but if I add a call to input to hold the program open everything works correctly (I'm running python 3.2.2 on linux).
So what exactly does mainloop do, and when is it necessary to call it?
EDIT:
To clarify, if I open up the GNOME terminal and type
$python3
>>> import tkinter
>>> root = tkinter.Tk()
a window immediately appears without having to call mainloop, and more complex tkinter functionality seems to work as well (for example, adding buttons to the window). In IDLE, a call to mainloop is necessary. It was my understanding that nothing should be drawn, and no events should be processed, until mainloop is called.
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.
Compare a program with an interactive GUI to a program that calculates the hundredth Fibonacci number. All the latter program has to go through a series of steps in order, top to bottom. The set of steps and their sequencing can be known in advance, and it'll remain constant no matter how many times you run the program.
But the GUI program is different: at any given moment, it has to be able to handle all sorts of different kinds of events and interactions. This requirement is often implemented using a programming construct called an event loop. An event loop is the central control structure of a program. It waits for an event to happen, and then dispatches the appropriate handler.
You didn't mention which interactive shell you're using, but I'm guessing it's IDLE. IDLE itself is a Tkinter program, and it already has an event loop going. So possibly the Tkinter code you are typing into the shell is getting bound to IDLE's event loop.
When you execute your code, the tkinter window will refuse to open without there being a mainloop function.
For example this will not work:
from tkinter import*
root=Tk()
This, however, will work:
from tkinter import*
root=Tk()
root.mainloop()
If you’re using python interective shell, you don’t need to call the root.mainloop() function. But if you are coding in a file, suppose one in IDLE, you need to call out the mainloop() function in order for your program to work as follows:
from tkinter import *
root = Tk()
# Elements in the GUI must go here eg:
lbl = Label(root, text=“Text”)
lbl.pack()
# At the end of the program, do this:
root.mainloop()
As follows:
from tkinter import *
tk = Tk()
canvas = Canvas(tk, width=500, height=500)
canvas.pack()
canvas.create_line(0, 0, 500, 500)
mainloop()
I've decided that, instead of sticking a call directly to mainloop anywhere in my script, I'll just add it as part of atexit - that is, when the Python interpreter decides it's time to start closing down, it's going to enter Tk's mainloop. This then prevents it from finishing the shut down sequence until the user actually tells Tk to quit (IE, with command-Q on a Mac, or by clicking on the red X in Windows.)
from Tkinter import Tk
root = Tk()
import atexit
atexit.register(root.mainloop)
There seems to be no need to call mainloop from a system command line. The Python interpreter will continue running without it, because it's waiting for further input from you (until you run exit()).

Categories

Resources