How can I create a binding in tkinter for when someone closes a toplevel window or if it is closed with toplevel1.destroy() or something similar. I am trying to make a small pop-up and when the user closes the main window or toplevel I want to prompt the user to save a file. I have figured out that I can set the actual close button to the function but cannot figure out how to get .destroy() and the closing of the main window to call the function. What should I do to bind the destroy function or window closing function?
Tested code:
import tkinter as tk
class TestWidget(tk.toplevel):
def __init__(self, *args, **kwargs):
tk.Toplevel.__init__(*args, **kwargs)
self.protocol("WM_DELETE_WINDOW", self.close)
def close(self):
print("Closed")
self.destroy()
if __name__ == "__main__":
root = tk.Tk()
TestWidget()
root.mainloop()
So I figured out that if you make a toplevel widget integrated into a class that you can find all the classes with the code below:
for child in root.winfo_children():
print(child)
This returns all the widgets and classes used:
.!testwidget
.!testwidget2
With this I can set up a function in the main window to call the child's close function one by one and this allows me to gain access to all the needed pieces
Related
I'm trying to have a "Downloading..." pop-up window show up as another function is running, and then when that function is done to close that pop-up window.
Thanks
import tkinter as t
from tkinter import ttk
root = t.Tk()
t.Label(root, text='Downloading...').pack()
pb = ttk.Progressbar(root, length=200, mode='indeterminate')
pb.pack()
pb.start()
root.update()
This code will immediately display this window:
Also, this won't stop execution... If you write more code below, python will continue executing your program; but there is a caveat - you have to call root.update() from time to time while you're doing something else, to allow tkinter to update the window, otherwise your window will seem frozen and the window manager will mark the window as "not responding".
Create a Tk() window and put a progress bar inside it.
You can find out how to create a popup window by searching StackOverflow for [tkinter] pop-up window. When you have done that, here is a generic 'downloading' wrapper function.
def downloading(parent, function, args, kwargs):
popup = <code or function call to create popup using parent)
ret = function(*args, **kwargs)
popup.destroy()
return ret
args is a tuple or list of positional arguments. kwargs is a key-value dict of keyword names and arguments.
I have a Tkinter window in the file gui.py. Upon the press of the Spacebar, I want to open another Tkinter window which is used to obtain an input from the user via the file imageinput.py.
So, I wrote the code to execute the run function of imageinput.py
def keyPressed(event, data):
if event.keysym == "space": image_run()
When I run this, I get the following error:
What is the best way to open such another Tkinter window this way?
Without knowing more of your code, you would create a new "top level" widget and use that widget like you used the original root top level window ( root = tkinter.Tk()) as the parent of whatever widget hierarchy you create. So...
def image_run(parent, *args, **kwargs):
top = tkinter.Toplevel(parent)
top.transient(parent)
canvas = tkinter.Canvas(top, ...)
:
:
Hope that helps!
I am currently experiencing some trouble getting the root window for tkinter to hide when a Toplevel window appears and then become visible when it is closed.
The toplevel window is suppose to be a configuration window and upon completing, it will configure the root window.
In my main tkinter object class I have it set up like the following:
class OthelloGUIGame:
def __init__(self):
'''Create a GUI and prompt for configurations'''
self._root_window = tkinter.Tk()
self._root_window.wm_title("Othello")
def start(self):
'''Starts the tkinter mainloop and displays the gui'''
self._root_window.withdraw()
game_config = GameSetup()
self._root_window.mainloop()
game_config.show()
self._root_window.deiconify()
This works perfectly for hiding the main Tk window, but it seems the deiconfy method is not getting called when I click the 'X' on the top level window.
Just in case it matters here is the basic setup for my toplevel class:
class GameSetup:
def __init__(self):
self._root_window = tkinter.Toplevel()
self._root_window.wm_title("Game Setup")
self._moves_first = tkinter.StringVar(self._root_window)
self._arrangement = tkinter.StringVar(self._root_window)
self._should_win = tkinter.StringVar(self._root_window)
self._moves_first.set("Black")
self._arrangement.set("Black in upper-left")
self._should_win.set("With more discs")
self._setUpEntries()
self._setUpDropDowns()
def show(self)->None:
'''Show the window'''
self._root_window.grab_set()
self._root_window.wait_wind()
Any ideas on why the window is not reappearing?
Generally speaking, you should never have code after mainloop. That is because mainloop won't return until the root window is destroyed. Since it is destroyed, any windows created as children of the root window will also be destroyed. At that point there's really nothing left for your GUI to do besides exit.
So, in your case you're creating this secondary window and waiting for it to be destroyed. That doesn't cause mainloop to exit, and there's no other code that is causing the main window to be deiconified, so it remains hidden.
You need to put the call to deiconify after the code that shows the GameConfig window. This needs to all be before calling mainloop. Typically you would start the game after the GUI initializes, which you can do with after_idle. In other words, the logic needs to be something like this:
def start(self):
'''Starts the tkinter mainloop and displays the gui'''
self._root_window.withdraw()
game_config = GameSetup()
# N.B. show will not return until the dialog is destroyed
game_config.show()
self._root_window.deiconify()
With this, you need to call mainloop somewhere else. Typically you would do that in your intialiation code. For example:
def __init__(self):
self._root_window = tkinter.Tk()
...
self.after_idle(self.start)
self._root_window.mainloop()
That creates the window, causes start to be caused once the window has been created, and then starts the event loop. The start method will then create the secondary window, wait for it to be destroyed, and then show the main window.
In the code below, tk is not the parent of the Toplevel object that is created by the function launch(). However, when I destroy tk using tk.destroy(), the Toplevel window vanishes.
Is the Toplevel widow being destroyed? If so, how is Toplevel.destroy() being called?
from tkinter import *
def launch():
Toplevel()
tk = Tk()
frame = Frame(tk, relief="ridge", borderwidth=2)
frame.pack(fill="both", expand=1)
label = Label(frame, text="Hello, World")
label.pack(fill=X, expand=1)
button1 = Button(frame, text="Exit", command=tk.destroy)
button2 = Button(frame, text="Launch", command=launch)
button1.pack(side="bottom")
button2.pack(side="bottom")
tk.mainloop()
What keeps your application running is the mainloop of the Tk instance, which is the parent of all widgets. When you destroy it, all the widgets are also destroyed.
Keeping in mind that for each Tk instance, there's an associated Tcl interpreter, I will try to give a more detailed answer on what happens when you close a window, based on the docs strings of the Tk and associated classes and methods of the tkinter module.
Tk derives from 2 classes: Misc and Wm. In the Misc class, you can find the interface and the documentation for the quit method:
def quit(self):
"""Quit the Tcl interpreter. All widgets will be destroyed."""
self.tk.quit()
You can find under the destroy method of the Tk class the following:
def destroy(self):
"""Destroy this and all descendants widgets. This will
end the application of this Tcl interpreter."""
The destroy method in the Tk class calls also, at certain point, the destroy method of the Misc class, and there you can find also another documentation:
def destroy(self):
"""Internal function.
Delete all Tcl commands created for
this widget in the Tcl interpreter."""
Which does not say that also the Tcl interpreter is stopped (like in the quit method described above).
When constructing a Tk instance, a method called _loadtk is called. In this method, it is set the protocol when the Tk window is closed:
self.protocol("WM_DELETE_WINDOW", self.destroy)
as you can see, destroy (and not quit) is associated with the closing event of the window.
This all means that when you close the window, the Tk instance and all its children are destroyed, but the Tcl interpreter is not stopped.
Tkinter.Tk is the big poppa granddaddy of all tkinter windows. It runs the logic and communicates with the OS. When it goes -- they all go.
This question already has answers here:
How do I handle the window close event in Tkinter?
(11 answers)
Closed 7 years ago.
When the user presses a close Button that I created, some tasks are performed before exiting. However, if the user clicks on the [X] button in the top-right of the window to close the window, I cannot perform these tasks.
How can I override what happens when the user clicks [X] button?
It sounds as if your save window should be modal.
If this is a basic save window, why are you reinventing the wheel?
Tk has a tkFileDialog for this purpose.
If what you want is to override the default behaviour of destroying the window, you can simply do:
root.protocol('WM_DELETE_WINDOW', doSomething) # root is your root window
def doSomething():
# check if saving
# if not:
root.destroy()
This way, you can intercept the destroy() call when someone closes the window (by any means) and do what you like.
Using the method procotol, we can redefine the WM_DELETE_WINDOW protocol by associating with it the call to a function, in this case the function is called on_exit:
import tkinter as tk
from tkinter import messagebox
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title("Handling WM_DELETE_WINDOW protocol")
self.geometry("500x300+500+200")
self.make_topmost()
self.protocol("WM_DELETE_WINDOW", self.on_exit)
def on_exit(self):
"""When you click to exit, this function is called"""
if messagebox.askyesno("Exit", "Do you want to quit the application?"):
self.destroy()
def center(self):
"""Centers this Tk window"""
self.eval('tk::PlaceWindow %s center' % app.winfo_pathname(app.winfo_id()))
def make_topmost(self):
"""Makes this window the topmost window"""
self.lift()
self.attributes("-topmost", 1)
self.attributes("-topmost", 0)
if __name__ == '__main__':
App().mainloop()
The command you are looking for is wm_protocol, giving it "WM_DELETE_WINDOW" as the protocol to bind to. It lets you define a procedure to call when the window manager closes the window (which is what happens when you click the [x]).
I found a reference on Tkinter here. It's not perfect, but covers nearly everything I ever needed. I figure section 30.3 (Event types) helps, it tells us that there's a "Destroy" event for widgets. I suppose .bind()ing your saving jobs to that event of your main window should do the trick.
You could also call mainwindow.overrideredirect(True) (section 24), which disables minimizing, resizing and closing via the buttons in the title bar.