I just want to close/destroy an overriden TopLevel widget when the main close button ('X' button) is clicked.
The overriden TopLevel widget is not created from the root of tKinter, but from a frame.
class MyToplevel(Tki.Toplevel):
def __init__(self, parent, *args, **kwargs): # parent is not the root of tKinter, it's a frame
super().__init__(*args, **kwargs)
self.protocol("WM_DELETE_WINDOW", self.on_closing) # Not working
self.wm_protocol("WM_DELETE_WINDOW", self.on_closing) # Not working
def on_closing(self):
# The key is, how can I call this method when main close button is clicked?
self.destroy()
I also have tried self.winfo_ismapped() and self.winfo_exists(), but when I click in the close button, nothing happens, because the main window exists.
If you want to close the toplevel when the close button ("X") on the root window is clicked, then you need to bind the protocol "WM_DELETE_WINDOW" on the root window as well:
class MyToplevel(Tki.Toplevel):
def __init__(self, parent, *args, **kwargs): # parent is not the root of tKinter, it's a frame
super().__init__(parent, *args, **kwargs)
# find root window
self.root = self.winfo_toplevel()
while self.root.master:
self.root = self.root.master.winfo_toplevel()
self.handler = self.root.protocol("WM_DELETE_WINDOW")
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
self.protocol("WM_DELETE_WINDOW", self.on_closing)
def on_closing(self):
# restore default handler for root window
self.root.protocol("WM_DELETE_WINDOW", self.handler)
self.destroy()
Note that this does not work when more than one toplevel is open simultaneously.
Related
I have a tkinter app, created with customtkinter:
import customtkinter
class App(customtkinter.CTk):
def __init__(self):
super().__init__()
Extra()
self.mainloop()
class Extra(customtkinter.CTkToplevel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.geometry("400x300")
self.label = customtkinter.CTkLabel(self, text="ToplevelWindow")
self.label.pack(padx=20, pady=20)
App()
I am trying to figure out code that checks if the Extra window has been closed. I've been looking around and cannot seem to find anything useful. Is there a way of doing this?
Based on answers in this thread How do I handle the window close event in Tkinter?:
If a WM_DELETE_WINDOW message arrives when you haven't defined a handler, then Tk handles the message by destroying the window for which it was received.
We could add a protocol named WM_DELETE_WINDOW and use the self.destroy() method as such:
class Extra(customtkinter.CTkToplevel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.geometry("400x300")
self.label = customtkinter.CTkLabel(self, text="ToplevelWindow")
self.label.pack(padx=20, pady=20)
self.protocol("WM_DELETE_WINDOW", self.closed) # <-- adding the protocol
def closed(self):
print("I've been closed!")
self.destroy()
Resulting in:
I've been closed!
And we then terminate the extra window.
I want to create a window which doesn't allow the user to access other windows until you give an input.
I tried win.attribute("ontop", True) but it allows the user to access other windows.
or is there any function like a force_focus_lock() in Tkinter python 3.8 which doesn't allow other window to
get focus until you give a input or the close present window.
I believe the below is what you are trying to do. Explanation is given in comments.
method #1: (PopOut1)
you can still move the main window
the new window assumes focus if there is a mouse release on main window
method #2: (PopOut2)
the main window is locked in place
the new window will assume focus, blink and "ding" if there is a mouse release on main window
import tkinter as tk
#first method
class PopOut1(tk.Toplevel):
def __init__(self, master, **kwargs):
tk.Toplevel.__init__(self, master, **kwargs)
self.geometry('400x300')
#set focus to this window
self.focus_set()
#releasing on any other tkinter window, within this process, forces focus back to this window
self.grab_set()
#second method
class PopOut2(tk.Toplevel):
def __init__(self, master, **kwargs):
tk.Toplevel.__init__(self, master, **kwargs)
self.geometry('400x300')
#set focus to this window
self.focus_set()
#disable the main window
master.attributes('-disabled', True)
#so this window can't end up behind the disabled window
#only necessary if this window is not transient
#self.attributes('-topmost', True)
#capture close event
self.protocol("WM_DELETE_WINDOW", self.close)
#event=None ~ in case you also want to bind this to something
def close(self, event=None):
#re-enable the main window
self.master.attributes('-disabled', False)
#destroy this window
self.destroy()
class App(tk.Tk):
TITLE = 'Application'
WIDTH, HEIGHT, X, Y = 800, 600, 50, 50
def __init__(self):
tk.Tk.__init__(self)
tk.Button(self, text="open popout 1", command=self.open1).grid()
tk.Button(self, text="open popout 2", command=self.open2).grid()
def open1(self):
PopOut1(self)
def open2(self):
#.transient(self) ~
# flash PopOut if focus is attempted on main
# automatically drawn above parent
# will not appear in taskbar
PopOut2(self).transient(self)
if __name__ == '__main__':
app = App()
app.title(App.TITLE)
app.geometry(f'{App.WIDTH}x{App.HEIGHT}+{App.X}+{App.Y}')
#app.resizable(width=False, height=False)
app.mainloop()
I am working on a program that uses a tkinter TopLevel window to display periodically updating log information to the user. My problem is that the main program is fullscreen, so whenever they interact with it after opening the log window, the log window isn't visible since it is now behind the main program.
Is there a way to force a Toplevel window (or actually, any Tkinter window) to remain permanently ontop of all other windows?
Consider this quick setup for example:
import tkinter as tk
from tkinter import ttk
class Example(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.pack()
btn = ttk.Button(self, text = "Press", command = self.openTopLevel)
btn.pack()
def openTopLevel(self):
topLevelWindow = tk.Toplevel(self)
root = tk.Tk()
main = Example(root)
root.mainloop()
When you Press the button and open the Toplevel Window, it is on top. But if you grab the Frame, move it around, etc, the Toplevel goes behind it. How do I stop that? Or is that not something Tkinter allows me to do?
To make a window stay in front of others in a tkinter application, use attributes('-topmost', 'true'). In your code, it is a one-line to add.
import tkinter as tk
from tkinter import ttk
class Example(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.pack()
btn = ttk.Button(self, text = "Press", command = self.openTopLevel)
btn.pack()
def openTopLevel(self):
topLevelWindow = tk.Toplevel(self)
# Make topLevelWindow remain on top until destroyed, or attribute changes.
topLevelWindow.attributes('-topmost', 'true')
root = tk.Tk()
main = Example(root)
root.mainloop()
I am trying to create a program/window that has one action button. This button when clicked, should open a second program/window and close the previous first program. This second program should have an action button that will open the first and close the second.
I found this script from another user and it works the same way I want the code to run. However, the root window is just being hidden with the deconify() when the otherframe is created.
What would be the best way to destroy the root window when the otherframe is created and still be able to be looped back.
Hopefully this made sense and thanks in advance.
import Tkinter as Tk
########################################################################
class OtherFrame(Tk.Toplevel):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
Tk.Toplevel.__init__(self)
self.geometry("100x100")
self.title("otherFrame")
########################################################################
class MyApp(object):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
self.root = parent
self.root.title("Main frame")
self.frame = Tk.Frame(parent)
self.frame.pack()
btn = Tk.Button(self.frame, text="Open Frame", command=self.openFrame)
btn.pack()
#----------------------------------------------------------------------
def hide(self):
""""""
self.root.withdraw()
#----------------------------------------------------------------------
def openFrame(self):
""""""
self.hide()
subFrame = OtherFrame()
handler = lambda: self.onCloseOtherFrame(subFrame)
btn = Tk.Button(subFrame, text="Close", command=handler)
btn.pack()
#----------------------------------------------------------------------
def onCloseOtherFrame(self, otherFrame):
""""""
otherFrame.destroy()
self.show()
#----------------------------------------------------------------------
def show(self):
""""""
self.root.update()
self.root.deiconify()
#----------------------------------------------------------------------
if __name__ == "__main__":
root = Tk.Tk()
root.geometry("800x600")
app = MyApp(root)
root.mainloop()
There is no best way to destroy the root window and then get it back. There is only one way to destroy it, which is to call the destroy() method. When you do that, all children windows will be destroyed and mainloop will exit.
While it's possible to destroy and recreate a root window, that's not how tkinter is designed to work, and it will not behave the way you expect it to behave. For example, all instances of StringVar, etc. will be destroyed. For another, you must have a root window, so by destroying the root window you will destroy all of its children including the Toplevel.
By far, the most common scenario is to simply hide the root window. If you truly want to destroy each window, put nothing in the root window and just leave it hidden. You can then use instances of Toplevel, which you can easily destroy and recreate at will.
Short answer... YOU CANT DO IT. The script that you have already is the best bet.
Try this:
import tkinter
class abc:
def __init__(self):
self.root = tkinter.Tk()
tkinter.Button(self.root, text="Click Me", command=lambda:abc.com(self)).pack()
def com(self):
self.root.destroy()
some = abc()
q = abc()
This doesn't actually jump between the two but creates a new one every time.
I have three windows:
Root window
Toplevel window
Color-chooser window.
The root window has a menu command that opens the toplevel. The toplevel has a button that opens the color-chooser.
When the color-chooser button is pressed and the color-chooser opens, something weird happens. The toplevel window gets sent BEHIND the root window.
The layering of the windows is like this before clicking the button:
ROOT
TOPLEVEL
The layering of the windows is like this after clicking the button:
TOPLEVEL
ROOT
COLORCHOOSER
Why is this happening? How can I stop this from happening? (I'm using Windows 7, and python 2.7)
Here is a simplified working code example:
from Tkinter import *
import ttk
import tkColorChooser
class Root(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
self.menu = Menu(self)
self.menu.add_command(label="Open Toplevel", command=self.create_toplevel)
self.config(menu=self.menu)
def create_toplevel(self):
self.new_toplevel = TopLevelWithButton(self)
class TopLevelWithButton(Toplevel):
def __init__(self, *args, **kwargs):
Toplevel.__init__(self, *args, **kwargs)
self.button = ttk.Button(self, text="Color Chooser", command=self.open_chooser)
self.button.grid(row=0, column=0)
def open_chooser(self):
tkColorChooser.askcolor()
root = Root()
root.mainloop()
You aren't telling the color dialog which window it belongs to, so by default it attaches itself to the root window. With some window managers this will cause the parent window to be raised to the top of the stacking order.
Try passing in the parent attribute, giving it a value of the toplevel window:
tkChooseColor.askcolor(parent=self)