With two tkinter windows, script does not execute after one's mainloop - python

I have a script which has two tkinter.Tk() objects, two windows. One is hidden from the start (using .withdraw()), and each has a button which hides itself and shows the other (using .deiconify()). I use .mainloop() on the one shown in the beginning. Everything works, but when I close either window, the code after the mainloop() doesn't run, and the script doesn't end.
I suppose this is because one window is still open. If that is the case, how do I close it? Is it possible to have a check somewhere which closes a window if the other is closed?
If not, how do I fix this?
The essence of my code:
from tkinter import *
window1 = Tk()
window2 = Tk()
window2.withdraw()
def function1():
window1.withdraw()
window2.deiconify()
def function2():
window2.withdraw()
window1.deiconify()
button1 = Button(master=window1, text='1', command=function1)
button2 = Button(master=window2, text='2', command=function2)
button1.pack()
button2.pack()
window1.mainloop()

Compiling answers from comments:
Use Toplevel instead of multiple Tk()s. That's the recommended practice, because it decreases such problems and is a much better choice in a lot of situations.
Using a protocol handler, associate the closing of one window with the closing of both. One way to do this is the following code:
from _tkinter import TclError
def close_both():
for x in (window1,window2):
try:
x.destroy()
except TclError:
pass
for x in (window1,window2):
x.protocol("WM_DELETE_WINDOW", close_both)

Related

How to handle X-ing out of a pop up in Tkinter [duplicate]

How do I handle the window close event (user clicking the 'X' button) in a Python Tkinter program?
Tkinter supports a mechanism called protocol handlers. Here, the term protocol refers to the interaction between the application and the window manager. The most commonly used protocol is called WM_DELETE_WINDOW, and is used to define what happens when the user explicitly closes a window using the window manager.
You can use the protocol method to install a handler for this protocol (the widget must be a Tk or Toplevel widget):
Here you have a concrete example:
import tkinter as tk
from tkinter import messagebox
root = tk.Tk()
def on_closing():
if messagebox.askokcancel("Quit", "Do you want to quit?"):
root.destroy()
root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()
Matt has shown one classic modification of the close button.
The other is to have the close button minimize the window.
You can reproduced this behavior by having the iconify method
be the protocol method's second argument.
Here's a working example, tested on Windows 7 & 10:
# Python 3
import tkinter
import tkinter.scrolledtext as scrolledtext
root = tkinter.Tk()
# make the top right close button minimize (iconify) the main window
root.protocol("WM_DELETE_WINDOW", root.iconify)
# make Esc exit the program
root.bind('<Escape>', lambda e: root.destroy())
# create a menu bar with an Exit command
menubar = tkinter.Menu(root)
filemenu = tkinter.Menu(menubar, tearoff=0)
filemenu.add_command(label="Exit", command=root.destroy)
menubar.add_cascade(label="File", menu=filemenu)
root.config(menu=menubar)
# create a Text widget with a Scrollbar attached
txt = scrolledtext.ScrolledText(root, undo=True)
txt['font'] = ('consolas', '12')
txt.pack(expand=True, fill='both')
root.mainloop()
In this example we give the user two new exit options:
the classic File → Exit, and also the Esc button.
Depending on the Tkinter activity, and especially when using Tkinter.after, stopping this activity with destroy() -- even by using protocol(), a button, etc. -- will disturb this activity ("while executing" error) rather than just terminate it. The best solution in almost every case is to use a flag. Here is a simple, silly example of how to use it (although I am certain that most of you don't need it! :)
from Tkinter import *
def close_window():
global running
running = False # turn off while loop
print( "Window closed")
root = Tk()
root.protocol("WM_DELETE_WINDOW", close_window)
cv = Canvas(root, width=200, height=200)
cv.pack()
running = True;
# This is an endless loop stopped only by setting 'running' to 'False'
while running:
for i in range(200):
if not running:
break
cv.create_oval(i, i, i+1, i+1)
root.update()
This terminates graphics activity nicely. You only need to check running at the right place(s).
If you want to change what the x button does or make it so that you cannot close it at all try this.
yourwindow.protocol("WM_DELETE_WINDOW", whatever)
then defy what "whatever" means
def whatever():
# Replace this with your own event for example:
print("oi don't press that button")
You can also make it so that when you close that window you can call it back like this
yourwindow.withdraw()
This hides the window but does not close it
yourwindow.deiconify()
This makes the window visible again
I'd like to thank the answer by Apostolos for bringing this to my attention. Here's a much more detailed example for Python 3 in the year 2019, with a clearer description and example code.
Beware of the fact that destroy() (or not having a custom window closing handler at all) will destroy the window and all of its running callbacks instantly when the user closes it.
This can be bad for you, depending on your current Tkinter activity, and especially when using tkinter.after (periodic callbacks). You might be using a callback which processes some data and writes to disk... in that case, you obviously want the data writing to finish without being abruptly killed.
The best solution for that is to use a flag. So when the user requests window closing, you mark that as a flag, and then react to it.
(Note: I normally design GUIs as nicely encapsulated classes and separate worker threads, and I definitely don't use "global" (I use class instance variables instead), but this is meant to be a simple, stripped-down example to demonstrate how Tk abruptly kills your periodic callbacks when the user closes the window...)
from tkinter import *
import time
# Try setting this to False and look at the printed numbers (1 to 10)
# during the work-loop, if you close the window while the periodic_call
# worker is busy working (printing). It will abruptly end the numbers,
# and kill the periodic callback! That's why you should design most
# applications with a safe closing callback as described in this demo.
safe_closing = True
# ---------
busy_processing = False
close_requested = False
def close_window():
global close_requested
close_requested = True
print("User requested close at:", time.time(), "Was busy processing:", busy_processing)
root = Tk()
if safe_closing:
root.protocol("WM_DELETE_WINDOW", close_window)
lbl = Label(root)
lbl.pack()
def periodic_call():
global busy_processing
if not close_requested:
busy_processing = True
for i in range(10):
print((i+1), "of 10")
time.sleep(0.2)
lbl["text"] = str(time.time()) # Will error if force-closed.
root.update() # Force redrawing since we change label multiple times in a row.
busy_processing = False
root.after(500, periodic_call)
else:
print("Destroying GUI at:", time.time())
try: # "destroy()" can throw, so you should wrap it like this.
root.destroy()
except:
# NOTE: In most code, you'll wanna force a close here via
# "exit" if the window failed to destroy. Just ensure that
# you have no code after your `mainloop()` call (at the
# bottom of this file), since the exit call will cause the
# process to terminate immediately without running any more
# code. Of course, you should NEVER have code after your
# `mainloop()` call in well-designed code anyway...
# exit(0)
pass
root.after_idle(periodic_call)
root.mainloop()
This code will show you that the WM_DELETE_WINDOW handler runs even while our custom periodic_call() is busy in the middle of work/loops!
We use some pretty exaggerated .after() values: 500 milliseconds. This is just meant to make it very easy for you to see the difference between closing while the periodic call is busy, or not... If you close while the numbers are updating, you will see that the WM_DELETE_WINDOW happened while your periodic call "was busy processing: True". If you close while the numbers are paused (meaning that the periodic callback isn't processing at that moment), you see that the close happened while it's "not busy".
In real-world usage, your .after() would use something like 30-100 milliseconds, to have a responsive GUI. This is just a demonstration to help you understand how to protect yourself against Tk's default "instantly interrupt all work when closing" behavior.
In summary: Make the WM_DELETE_WINDOW handler set a flag, and then check that flag periodically and manually .destroy() the window when it's safe (when your app is done with all work).
PS: You can also use WM_DELETE_WINDOW to ask the user if they REALLY want to close the window; and if they answer no, you don't set the flag. It's very simple. You just show a messagebox in your WM_DELETE_WINDOW and set the flag based on the user's answer.
You should use destroy() to close a tkinter window.
from Tkinter import *
root = Tk()
Button(root, text="Quit", command=root.destroy).pack()
root.mainloop()
Explanation:
root.quit()
The above line just Bypasses the root.mainloop() i.e root.mainloop() will still be running in background if quit() command is executed.
root.destroy()
While destroy() command vanish out root.mainloop() i.e root.mainloop() stops.
So as you just want to quit the program so you should use root.destroy() as it will it stop the mainloop()`.
But if you want to run some infinite loop and you don't want to destroy your Tk window and want to execute some code after root.mainloop() line then you should use root.quit().
Ex:
from Tkinter import *
def quit():
global root
root.quit()
root = Tk()
while True:
Button(root, text="Quit", command=quit).pack()
root.mainloop()
#do something
The easiest code is:
from tkinter import *
window = Tk()
For hiding the window : window.withdraw()
For appearing the window : window.deiconify()
For exiting from the window : exit()
For exiting from the window(If you've made a .exe file) :
from tkinter import *
import sys
window = Tk()
sys.exit()
And of course you have to place a button and use the codes above in a function so you can type the function's name in the command part of the button
Try The Simple Version:
import tkinter
window = Tk()
closebutton = Button(window, text='X', command=window.destroy)
closebutton.pack()
window.mainloop()
Or If You Want To Add More Commands:
import tkinter
window = Tk()
def close():
window.destroy()
#More Functions
closebutton = Button(window, text='X', command=close)
closebutton.pack()
window.mainloop()
you can use:
root = Tk()
def func():
print('not clossed')
root.protocol('wm_delete_window', func)
root.mainloop()
def on_closing():
if messagebox.askokcancel("Quit", "would you like to quit"):
window.destroy()
window.protocol("WM_DELETE_WINDOW", on_closing)
you can handle a window close event like this, if you wanna do something else just change the things that happen in the on_closing() function.
i say a lot simpler way would be using the break command, like
import tkinter as tk
win=tk.Tk
def exit():
break
btn= tk.Button(win, text="press to exit", command=exit)
win.mainloop()
OR use sys.exit()
import tkinter as tk
import sys
win=tk.Tk
def exit():
sys.exit
btn= tk.Button(win, text="press to exit", command=exit)
win.mainloop()

Correct way to run two Tk() mainloops independently, with second being started from first script?

script_a.py
from tkinter import *
from script_b import test
root = Tk()
var1 = stuff
var2 = stuff
def start_scriptb():
test([var1, var2])
Button(root, text="Start",
command=start_scriptb)
root.mainloop()
script_b.py
from tkinter import *
def test(x):
main = Tk()
Label(main, text=x)
Button(main, text="Exit", command=main.destroy())
main.mainloop()
This is a very basic version of what I'm trying to achieve. I actually am spawning a progress window that uses subprocess.Popen in script 2 with the passed through variables from script one and viewing the progess through a scrolled text widget on the 2nd program. I'm trying to spawn a new process, independent from the root GUI each time that button is hit from script_a.
It works fine in my tests so far, but I wanted to see if this could cause any issues or if this is actually spawning two processes?
I know there should only be one Tk() per process.
Using a TopLevel() window to show the progress works fine with the threading module, but the TopLevel() window will freeze as well as root (and any other open TopLevels()) if root is doing any sort of processing that takes any length of time.
With many hours of testing, I did have success in running two Tk() loops, but it had potential to be problematic, as "Bryan Oakley" had posted in many threads about.
Ultimately, I decided when I was in need of running something alone, I'd start my GUI with arguments and process it in an entirely new process instead of passing any arguments directly. Seems like a safer option.

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.

Opening new window in Tkinter freezes the program (python 3.6)

I have something like this:
first.py
from tkinter import *
def new_window(event):
root.destroy()
import second
root = Tk()
b = Button(root, text='New window')
b.pack()
b.bind('<Button-1>', new_window)
root.mainloop()
second.py
from tkinter import *
root = Tk()
root.mainloop()
But when I open the second window, the first one is destroyed (I hope so), but the second one is frozen (it is shown, but there is no close-button on the top and I only see the launch-icon). Why is it so? Don't I kill the first loop?
The problem is likely because import second never returns since the last thing it does is call root.mainloop(). Since it never returns, the callback in the first window never finishes. And since it never finishes, it's not able to process any other events.

Find Out When A Tkinter Window Quits [duplicate]

How do I handle the window close event (user clicking the 'X' button) in a Python Tkinter program?
Tkinter supports a mechanism called protocol handlers. Here, the term protocol refers to the interaction between the application and the window manager. The most commonly used protocol is called WM_DELETE_WINDOW, and is used to define what happens when the user explicitly closes a window using the window manager.
You can use the protocol method to install a handler for this protocol (the widget must be a Tk or Toplevel widget):
Here you have a concrete example:
import tkinter as tk
from tkinter import messagebox
root = tk.Tk()
def on_closing():
if messagebox.askokcancel("Quit", "Do you want to quit?"):
root.destroy()
root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()
Matt has shown one classic modification of the close button.
The other is to have the close button minimize the window.
You can reproduced this behavior by having the iconify method
be the protocol method's second argument.
Here's a working example, tested on Windows 7 & 10:
# Python 3
import tkinter
import tkinter.scrolledtext as scrolledtext
root = tkinter.Tk()
# make the top right close button minimize (iconify) the main window
root.protocol("WM_DELETE_WINDOW", root.iconify)
# make Esc exit the program
root.bind('<Escape>', lambda e: root.destroy())
# create a menu bar with an Exit command
menubar = tkinter.Menu(root)
filemenu = tkinter.Menu(menubar, tearoff=0)
filemenu.add_command(label="Exit", command=root.destroy)
menubar.add_cascade(label="File", menu=filemenu)
root.config(menu=menubar)
# create a Text widget with a Scrollbar attached
txt = scrolledtext.ScrolledText(root, undo=True)
txt['font'] = ('consolas', '12')
txt.pack(expand=True, fill='both')
root.mainloop()
In this example we give the user two new exit options:
the classic File → Exit, and also the Esc button.
Depending on the Tkinter activity, and especially when using Tkinter.after, stopping this activity with destroy() -- even by using protocol(), a button, etc. -- will disturb this activity ("while executing" error) rather than just terminate it. The best solution in almost every case is to use a flag. Here is a simple, silly example of how to use it (although I am certain that most of you don't need it! :)
from Tkinter import *
def close_window():
global running
running = False # turn off while loop
print( "Window closed")
root = Tk()
root.protocol("WM_DELETE_WINDOW", close_window)
cv = Canvas(root, width=200, height=200)
cv.pack()
running = True;
# This is an endless loop stopped only by setting 'running' to 'False'
while running:
for i in range(200):
if not running:
break
cv.create_oval(i, i, i+1, i+1)
root.update()
This terminates graphics activity nicely. You only need to check running at the right place(s).
If you want to change what the x button does or make it so that you cannot close it at all try this.
yourwindow.protocol("WM_DELETE_WINDOW", whatever)
then defy what "whatever" means
def whatever():
# Replace this with your own event for example:
print("oi don't press that button")
You can also make it so that when you close that window you can call it back like this
yourwindow.withdraw()
This hides the window but does not close it
yourwindow.deiconify()
This makes the window visible again
I'd like to thank the answer by Apostolos for bringing this to my attention. Here's a much more detailed example for Python 3 in the year 2019, with a clearer description and example code.
Beware of the fact that destroy() (or not having a custom window closing handler at all) will destroy the window and all of its running callbacks instantly when the user closes it.
This can be bad for you, depending on your current Tkinter activity, and especially when using tkinter.after (periodic callbacks). You might be using a callback which processes some data and writes to disk... in that case, you obviously want the data writing to finish without being abruptly killed.
The best solution for that is to use a flag. So when the user requests window closing, you mark that as a flag, and then react to it.
(Note: I normally design GUIs as nicely encapsulated classes and separate worker threads, and I definitely don't use "global" (I use class instance variables instead), but this is meant to be a simple, stripped-down example to demonstrate how Tk abruptly kills your periodic callbacks when the user closes the window...)
from tkinter import *
import time
# Try setting this to False and look at the printed numbers (1 to 10)
# during the work-loop, if you close the window while the periodic_call
# worker is busy working (printing). It will abruptly end the numbers,
# and kill the periodic callback! That's why you should design most
# applications with a safe closing callback as described in this demo.
safe_closing = True
# ---------
busy_processing = False
close_requested = False
def close_window():
global close_requested
close_requested = True
print("User requested close at:", time.time(), "Was busy processing:", busy_processing)
root = Tk()
if safe_closing:
root.protocol("WM_DELETE_WINDOW", close_window)
lbl = Label(root)
lbl.pack()
def periodic_call():
global busy_processing
if not close_requested:
busy_processing = True
for i in range(10):
print((i+1), "of 10")
time.sleep(0.2)
lbl["text"] = str(time.time()) # Will error if force-closed.
root.update() # Force redrawing since we change label multiple times in a row.
busy_processing = False
root.after(500, periodic_call)
else:
print("Destroying GUI at:", time.time())
try: # "destroy()" can throw, so you should wrap it like this.
root.destroy()
except:
# NOTE: In most code, you'll wanna force a close here via
# "exit" if the window failed to destroy. Just ensure that
# you have no code after your `mainloop()` call (at the
# bottom of this file), since the exit call will cause the
# process to terminate immediately without running any more
# code. Of course, you should NEVER have code after your
# `mainloop()` call in well-designed code anyway...
# exit(0)
pass
root.after_idle(periodic_call)
root.mainloop()
This code will show you that the WM_DELETE_WINDOW handler runs even while our custom periodic_call() is busy in the middle of work/loops!
We use some pretty exaggerated .after() values: 500 milliseconds. This is just meant to make it very easy for you to see the difference between closing while the periodic call is busy, or not... If you close while the numbers are updating, you will see that the WM_DELETE_WINDOW happened while your periodic call "was busy processing: True". If you close while the numbers are paused (meaning that the periodic callback isn't processing at that moment), you see that the close happened while it's "not busy".
In real-world usage, your .after() would use something like 30-100 milliseconds, to have a responsive GUI. This is just a demonstration to help you understand how to protect yourself against Tk's default "instantly interrupt all work when closing" behavior.
In summary: Make the WM_DELETE_WINDOW handler set a flag, and then check that flag periodically and manually .destroy() the window when it's safe (when your app is done with all work).
PS: You can also use WM_DELETE_WINDOW to ask the user if they REALLY want to close the window; and if they answer no, you don't set the flag. It's very simple. You just show a messagebox in your WM_DELETE_WINDOW and set the flag based on the user's answer.
You should use destroy() to close a tkinter window.
from Tkinter import *
root = Tk()
Button(root, text="Quit", command=root.destroy).pack()
root.mainloop()
Explanation:
root.quit()
The above line just Bypasses the root.mainloop() i.e root.mainloop() will still be running in background if quit() command is executed.
root.destroy()
While destroy() command vanish out root.mainloop() i.e root.mainloop() stops.
So as you just want to quit the program so you should use root.destroy() as it will it stop the mainloop()`.
But if you want to run some infinite loop and you don't want to destroy your Tk window and want to execute some code after root.mainloop() line then you should use root.quit().
Ex:
from Tkinter import *
def quit():
global root
root.quit()
root = Tk()
while True:
Button(root, text="Quit", command=quit).pack()
root.mainloop()
#do something
The easiest code is:
from tkinter import *
window = Tk()
For hiding the window : window.withdraw()
For appearing the window : window.deiconify()
For exiting from the window : exit()
For exiting from the window(If you've made a .exe file) :
from tkinter import *
import sys
window = Tk()
sys.exit()
And of course you have to place a button and use the codes above in a function so you can type the function's name in the command part of the button
Try The Simple Version:
import tkinter
window = Tk()
closebutton = Button(window, text='X', command=window.destroy)
closebutton.pack()
window.mainloop()
Or If You Want To Add More Commands:
import tkinter
window = Tk()
def close():
window.destroy()
#More Functions
closebutton = Button(window, text='X', command=close)
closebutton.pack()
window.mainloop()
you can use:
root = Tk()
def func():
print('not clossed')
root.protocol('wm_delete_window', func)
root.mainloop()
def on_closing():
if messagebox.askokcancel("Quit", "would you like to quit"):
window.destroy()
window.protocol("WM_DELETE_WINDOW", on_closing)
you can handle a window close event like this, if you wanna do something else just change the things that happen in the on_closing() function.
i say a lot simpler way would be using the break command, like
import tkinter as tk
win=tk.Tk
def exit():
break
btn= tk.Button(win, text="press to exit", command=exit)
win.mainloop()
OR use sys.exit()
import tkinter as tk
import sys
win=tk.Tk
def exit():
sys.exit
btn= tk.Button(win, text="press to exit", command=exit)
win.mainloop()

Categories

Resources