Close tkinter GUI without killing the app - python

I'm using Tkinter to create a GUI program on python 2.7.
At some point, I want to open an informative window and close it automatically later on, the problem is, when I call quit on it the whole application closes.
root = tk.Tk()
root.title("WINDOW")
def create_wnd(xxx, yyy):
yyy.destroy()
def run_wnd():
dialog = tk.Toplevel()
dialog.title("wnd2")
wnd_run_button = tk.Button(root, text="RUN", command=lambda:run_wnd())
wnd_run_button.pack()
root.mainloop()

Put "the rest of the application" after the call to mainloop. Killing the window causes mainloop to exit, but any code after that will continue to run.
Or, put all of the window code inside a function that finishes by calling `mainloop, so the function won't exit until the window is destroyed. The code that calls the function will continue as soon as the function returns.

Related

how to properly close a tkinter window with background threads updaing the gui

I have a Tkinter app which built of single window and several frames, each frame is a class. Some frames has threads that changes the GUI components (e.g entry) on the context of receiving a message from socket.
root = tk.Tk()
gui = MainFrame(root) # this class holds the frames
root.protocol("WM_DELETE_WINDOW", functools.partial(gui.close, root))
...
root.mainloop()
The problem: when the user press the "X" button the following close func is called:
def close(self):
self.frame1.close() # turns the while condition of the thread to be false and call join()
self.frame2.close() # "
...
root.destroy()
The internal threads are running in while loop (with condition) trying to update an GUI components and after call to close() they get stuck on a single line of code without rising an exception.
e.g self.entry.configure(state='disabled'). Then the join() call never ends.
This way the code is stuck and the GUI is not closing. I can call root.destroy() at the beginning of the function then the GUI will be closed but the code is still running (stuck)
How should i prevent this stuck or force killing my threads?
Thank you

Tkinter TopLevel not showing when it's supposed to

I have a very simple python code: a tkitner button that process some images in the background. I wanted to open a tkinter toplevel to show the user that it was doing something, but for my surprise is not working as I thought it would. The command on the tk.Button is the next method:
def processing(self):
"""Starts the images processing"""
# Open a Tk.Toplevel
aux_topLevel = Splash(self.window) # a simple Tk.Toplevel class, that works perfectly
self._process_images() # starts processing the images
# I wanted to kill here the topLevel created before
aux_topLevel.destroy()
My surprise: the window is displayed once the processing images is done (tried it out adding prints and time.sleep), however, i couldn't display the TopLevel when I wanted to.
Is there anything am I doing wrong? Any help is appreciated. Thank you.
Consider the following example and try to run it.
What you'd think should happen is that the new Toplevel window should open, some event happens for a period of time and then the window is destroyed.
What actually happens is the window is opened, but never displayed, the task occurs and then the window is destroyed.
from tkinter import *
import time
def processing():
new = Toplevel(root)
new.geometry("200x150")
lbl = Label(new,text="--")
lbl.grid()
for i in range(50):
time.sleep(0.1)
#Un-comment the line below to fix
#root.update()
print(i)
lbl['text'] = "{}".format(i)
new.destroy()
root = Tk()
root.geometry('200x100')
btnGo = Button(root,text="Go",command=processing)
btnGo.grid()
root.mainloop()
If you un-comment out the root.update() line and re-run the code, the window will be displayed.
There are better ways to deal with tasks that takes a while to process, such as threading.

What kind of code does tkinter's mainloop collect?

Suppose I have Python code like this
# <Pure Python statement A>
root = tk.Tk()
mainframe = tk.Frame(root)
# <Pure Python statement B>
# <other tkinter code>
root.mainloop()
Which statements are then ending up on tkinter's mainloop? Is it just the 3 tkinter statements?
EDIT
There must be more things going on, because some code between the tkinter code is affected: When I run the following code (taken from another question)
import tkinter as tk
import tkinter.filedialog
filename = ""
def op():
global filename
filename =tk.filedialog.askopenfilename()
root = tk.Tk()
mainframe = tk.Frame(root)
mainframe.grid(column=0, row=0)
tk.Button(mainframe, text="Open file", command=op).grid(column=0, row=1)
root.mainloop()
print(filename)
after closing the program the selected filename is displayed. But when running
import tkinter as tk
import tkinter.filedialog
filename = "this_is_a_test"
def op():
global filename
filename =tk.filedialog.askopenfilename()
root = tk.Tk()
mainframe = tk.Frame(root)
mainframe.grid(column=0, row=0)
tk.Button(mainframe, text="Open file", command=op).grid(column=0, row=1)
print(filename)
root.mainloop()
after closing the program, nothing is displayed. So somehow the pure Python statements before mainloop seem to get absorbed.
What kind of code does tkinter's mainloop collect?
It doesn't collect anything. It simply processes events, and calls functions bound to those events. It also calls functions added to the queue via after.
Which statements are then ending up on tkinter's mainloop? Is it just the 3 tkinter statements?
Nothing "ends up on tkinter's mainloop". That's a nonsensical statement, nothing can end up on it. It is just a function that processes events, and doesn't return until the window is destroyed. All code before the call to mainloop executes according to the normal rules of python.
Calling mainloop is effectively the same as if you put this in its place (but it is much more efficient):
while True:
self.update()
Much like with the above, any code after mainloop() will not execute until the loop exits, which happens when the window has been destroyed.
The reason your print seems to work after the call to mainloop but not before is simply that before mainloop, filename is the empty string. The print run normally, it's just that there's nothing to print. That print statement happens a few milliseconds after the program starts, way before the user has a chance to do anything. When called after, it seems to work because that code doesn't run until the window has been destroyed. At that point it presumably has a value, so you see something printed.
The simple answer is: There's no kind of code that mainloop collects.
It 'collect' s all configuration that is related to the Tcl interpreter it is a method of. As in if your GUI is a configuration of root = tk.Tk(), and the mainloop is a method of root then all configurations under it will be accounted for such as children widgets and their configurations.
Your print statement doesn't get absorbed. It simply prints what would've been printed if the button was never used. Try the 2nd code with simply closing the GUI without using the button. mainloop doesn't absorb anything. It simply waits for events for the GUI configured.

window freezes in Python 3

I am using python3 on a mac and run scripts with the IDLE which comes automatically with the python3 installation.
I am trying to make an alert to the user and found the command
tkinter.messagebox.showinfo("title","some text")
So I i tried a minimal script to check if I can get along with that command
import tkinter
tkinter.messagebox.showinfo("test" , "blabla")
The window is displayed correctly but it doesn't respond when I click on the "OK" button.
Addtionally there is a second empty window which appears when I start the script.
What is the explanation for this or at least how can I fix that?
tkinter isn't designed to work this way. Every tkinter requires a root window. If you don't explicitly create one (and you didn't), one will be created for you. That's what the blank window is.
Also, a tkinter GUI can't function properly unless it has a running event loop. This is necessary because some functions, such as responding to buttons and redrawing the window, only happens in response to events. If the event loop isn't running, events can't be processed.
Bottom line: the dialogs aren't designed to be used outside of the context of a proper tkinter app.
Wrapper for standalone use
The following code can be used to display one of the dialogs in standalone mode. It works by creating and hiding a root window, displaying the dialog, and then destroying the root window.
import tkinter as tk
from tkinter import messagebox
def show_dialog(func, *args, **kwargs):
# create root window, then hide it
root = tk.Tk()
root.withdraw()
# create a mutable variable for storing the result
result = []
# local function to call the dialog after the
# event loop starts
def show_dialog():
# show the dialog; this will block until the
# dialog is dismissed by the user
result.append(func(*args, **kwargs))
# destroy the root window when the dialog is dismissed
# note: this will cause the event loop (mainloop) to end
root.destroy()
# run the function after the event loop is initialized
root.after_idle(show_dialog)
# start the event loop, then kill the tcl interpreter
# once the root window has been destroyed
root.mainloop()
root.quit()
# pop the result and return
return result.pop()
To use it, pass the dialog you want as the first option, followed by dialog-specific options.
For example:
result = show_dialog(messagebox.askokcancel, "title", "Are you sure?")
if result:
print("you answered OK")
else:
print("you cancelled")

Tkinter: one or more mainloops?

I have an already large Tkinter program, so that I have an init file, where the root = Tk() window is defined (containing basically a Text widget and a few other things), some more code, and last the call to mainloop() function.
Everything works, until I needed to call a procedure before the mainloop, and I wanted to raise a wait window at the begin, to be destroyed at procedure's end.
I wrote something like:
msg = Message(root, text='wait a few seconds...')
msg.pack()
But it doesn't and cannot work, since mainloop() has not been called yet!
If I instead do:
msg = Message(root, text='wait a few seconds...')
msg.pack()
mainloop()
The program stops at this first mainloop, doesn't finish the procedure call.
mainloop() should be used as your last program line, after which the Tkinter program works by a logic driven by user clicks and interactions, etc.
Here, I need a sequence of raise window > do stuff > destroy window > mainloop
You are correct that mainloop needs to be called once, after your program has initialized. This is necessary to start the event loop, which is necessary for windows to draw themselves, respond to events, and so on.
What you can do is break your initialization into two parts. The first -- creating the wait window -- happens prior to starting the event loop. The second -- doing the rest of the initialization -- happens once the event loop has started. You can do this by scheduling the second phase via the after method.
Here's a simple example:
import Tkinter as tk
import time
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
# initialize Tkinter
tk.Tk.__init__(self, *args, **kwargs)
# hide main window
self.wm_withdraw()
# show "please wait..." window
self.wait = tk.Toplevel(self)
label = tk.Label(self.wait, text="Please wait...")
label.pack()
# schedule the rest of the initialization to happen
# after the event loop has started
self.after(100, self.init_phase_2)
def init_phase_2(self):
# simulate doing something...
time.sleep(10)
# we're done. Close the wait window, show the main window
self.wait.destroy()
self.wm_deiconify()
app = SampleApp()
app.mainloop()
You should use Tkinter's method to run asyncore's loop function, but you should use asyncore.poll(0) instead of asyncore.loop(). If you call function asyncore.poll(0) every x ms, it has no longer an effect on Tkinter's main loop.

Categories

Resources