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.
Related
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
I have created a tkinter application where the user can make multiple toplevel windows and have the option of closing them from inside the toplevel. I would like to make a button on the main window that closes all toplevel windows. How would I do this? Is there a way to do this without lists? If these toplevels are parts of classes is there also a way to call a function present in all of them?
Here's how to do the first part of your question about making a button in the main window to delete all the Toplevels without making a list of them. This works by using the universal winfo_children() widget method to find all the child widgets of the root (main) window.
It's unclear to me what you meant about calling a function present in all of them — Toplevel widgets are instances of a predefined tkinter class which supports a predefined set of methods — and you can call them the same way the sample code below does with child.destroy().
import tkinter as tk
root = tk.Tk()
root.title('Main')
root.geometry('200x100')
def close_all(master):
for child in master.winfo_children():
if isinstance(child, tk.Toplevel):
child.destroy() # Call method.
button = tk.Button(root, text=f"Close Toplevels",
command=lambda master=root: close_all(master))
button.pack()
for i in reversed(range(4)): # Create them bottom to top.
toplevel = tk.Toplevel()
toplevel.title(f'Toplevel {i+1}')
toplevel.geometry('200x75')
toplevel.lift()
button = tk.Button(toplevel, text="Close me", command=toplevel.destroy)
button.pack()
root.mainloop()
In my real code I have a main window where the user can choose to open other kind of windows. In the main one, I defined the ttk style using the ttk.style() statement. It works, but if I define the same style in the other classes dedicated for the other windows, the ttk.style() doesn't work anymore. Why? Below is an example:
from tkinter import *
from tkinter import ttk
class MainWindow:
def __init__(self):
self.parent=Tk()
self.parent.geometry("400x400")
self.parent.title(self.main_name)
self.parent.configure(background="#f0f0f0")
style=ttk.Style()
style.configure("TButton", background="red", padding=0)
MyButton=ttk.Button(self.parent, text="open a new window", command=self.Open)
MyButton.pack()
self.parent.mainloop()
def Open(self):
obj=NewWindow()
class NewWindow():
def __init__(self):
self.parent=Tk()
self.parent.geometry("400x400")
self.parent.configure(background="#f0f0f0")
style=ttk.Style()
style.configure("TButton", background="red", padding=0)
MyButton=ttk.Button(self.parent, text="This button has not a custom style.. why?")
MyButton.pack()
if __name__=="__main__":
app=MainWindow()
Why the window from the NewWindow class doesn't use the custom ttk style like the other one from the MainWindow class?
Then I want to write just one time the ttk instructions, because in my real code, all classes use the same style. What is the best way to do it?
Below is a screenshot about my example:
Every instance of Tk is a separate environment, and cannot share data with other instances of Tk. If you want multiple windows to be able to share information with the first window, you must create instances of Toplevel rather than Tk.
The reason your second window doesn't accept the new styling is that the Style object you created belongs to the original root window. If you want it to affect the new root window you must explicitly tell it so by specifying the master attribute.
style=ttk.Style(master=self.parent)
#snakes and ladder
from tkinter import * #pygame is the module that has collections of functions that is used to create a window import everything from tkinter
import time
class window(Frame): #Frame comes from tkinter is what you think as a window
def __init__(self, master = None):#The master widget defines the settings upon initialisation
Frame.__init__(self, master) #This is called the frame class that has been built in python
self.master = master
def __init__window(self): #creation of the init window
self.master.title("Reagan Kambayi") #It changes the title of the title of our widget
self.pack(fill=BOTH, expand=1)#The pack function will pack this in our frame
#placing the button
stop = Button(self, master, message= "Stop")
#intialising the button that will start the stopwatch
stop.place(x=0, y=0)
screen = Tk() #It must be written with capitalised T because there will be an error and it holds the components of the tkinter module
screen.geometry("700x500")
app = window(screen) #The app variable is assigned to the window creation which has the tkinter module
screen.mainloop()
Ok, here we go.
from tkinter import * #pygame is the module that has collections of functions that is used to create a window import everything from tkinter
Pygame has nothing to do with tkinter and you're not importing pygame here, you're importing tkinter.
class window(Frame): #Frame comes from tkinter is what you think as a window
No, it isn't. A Frame widget is just that, a frame inside a window. It doesn't draw a window in and of itself. The parameter Frame in your example isn't even a Frame widget at all, it's value is Tk() which is the function called to draw the first window in tkinter.
def __init__(self, master = None):#The master widget defines the settings upon initialisation
I'm actually at a loss for what you're attempting to do here. master should equal Frame which equals screen which equals Tk() but if I'm correct you're overriding that and telling it to equal None?
Frame.__init__(self, master) #This is called the frame class that has been built in python
I don't think you're doing what you think you're doing here. This answer explains it better than I could.
def __init__window(self): #creation of the init window
If I'm reading your program correctly then window.__init__window() is never called, so none of this function ever actually happens.
self.pack(fill=BOTH, expand=1)#The pack function will pack this in our frame
You're attempting to call .pack() on self which is calling .pack() on Frame. Typically we wouldn't assign a value to self in this way (although this is valid), read this to find out what self should be used for.
#placing the button
stop = Button(self, master, message= "Stop")
This isn't placing the Button widget, this is assigning the Button widget to a variable. Also, Button widgets are declared as Button(parent, *attributes) and there is no message attribute built in. Meaning what you meant to call was Button(self.master, text="Stop").
#intialising the button that will start the stopwatch
stop.place(x=0, y=0)
This is where you're placing the button, but the function that contains this is never called, so it never happens.
app = window(screen) #The app variable is assigned to the window creation which has the tkinter module
What you're actually doing here is calling the class window, all this does in your current program is call window.__init__(), which in itself does essentially nothing.
This is meant with no offence but I think you're lacking a very basic understanding of tkinter and possibly even Pythonic OOP.
I believe what you're trying to do in your program is the below:
from tkinter import *
class App:
def __init__(self, root):
self.root = root
self.root.title("Reagan Kambayi")
self.stop = Button(self.root, text="Stop")
self.stop.place(x=0, y=0)
root = Tk()
App(root)
root.mainloop()
I am working on a program that requires multiple windows, and the first one to appear is the login window, I used the Toplevel widget in order to make other windows its children, but this code keeps showing two windows instead of one.
from Tkinter import Frame, Toplevel
from ttk import Label, Entry, Button
class loginWindow(Toplevel):
def __init__(self):
Toplevel.__init__(self)
self.title("Title")
self.frame = Frame(self)
self.frame.pack()
self.__make_layout()
self.mainloop()
def __make_layout(self):
self.frame.user_name_label = Label(text="User name:")
self.frame.user_name_text = Entry()
self.frame.user_name_label.grid(row=0, column=0)
self.frame.user_name_text.grid(row=0, column=1)
self.frame.password_label = Label(text="Password:")
self.frame.password_text = Entry()
self.frame.password_label.grid(row=1, column=0)
self.frame.password_text.grid(row=1, column=1)
self.frame.login_button = Button(text="Login")# , command=self.__create_window)
self.frame.login_button.grid(row=2, column=0, columnspan=2)
if __name__ == '__main__':
win1 = loginWindow()
All of the widgets created in _make_layout are created without a parent. This means they're children of the default root. You need to pass a parent to each of them, the same way you do to the Frame. Like this:
self.frame.user_name_label = Label(self.frame, text="User name:")
self.frame.user_name_text = Entry(self.frame)
# etc.
When I run your exact code, I don't get a second window, on any platform I try. The closest I get is on OS X, where an entry for the default root window appears in the Window menu, but the window itself still doesn't appear and the widgets all end up on the Toplevel (although not on the Frame where you wanted them). But it certainly would be legal for Tkinter to show a second window here, and put some or all of your widgets on it.
This must be a platform dependent issue, since abarnert isn't having issues with multiple windows. I use OS X with XQuartz and the following code gives me two windows:
from Tkinter import Toplevel, Tk
Toplevel().mainloop()
However, this code gives me one window:
from Tkinter import Toplevel, Tk
Tk().mainloop()
I believe your first window should be declared Tk() and subsequent windows should be Toplevel().