Tkinter Toplevel : Destroy window when not focused - python

I have a Toplevel widget that I want to destroy whenever the user clicks out of the window. I tried finding solutions on the Internet, but there seems to be no article discussing about this topic.
How can I achieve this. Thanks for any help !

You can try something like that :
fen is your toplevel
fen.bind("<FocusOut>", fen.quit)

I had a similar problem, and fixed it. The following example works fine.
It displays a Toplevel window on top of the main window, as a customized config menu.
Whenever the user clicks somewhere else, the config menu disappears.
Warning: Don't use .transient(parent) on the Toplevel window, otherwise, the symptom that you described happens: when clicking on the main window, the menu does not disappear. You can try in the example below to uncomment the "self.transient(parent)" line to reproduce your issue.
Example:
import Tix
class MainWindow(Tix.Toplevel):
def __init__(self, parent):
# Init
self.parent = parent
Tix.Toplevel.__init__(self, parent)
self.protocol("WM_DELETE_WINDOW", self.destroy)
w = Tix.Button(self, text="Config menu", command=self.openMenu)
w.pack()
def openMenu(self):
configWindow = ConfigWindow(self)
configWindow.focus_set()
class ConfigWindow(Tix.Toplevel):
def __init__(self, parent):
# Init
self.parent = parent
Tix.Toplevel.__init__(self, parent)
# If you uncomment this line it reproduces the issue you described
#self.transient(parent)
# Hides the window while it is being configured
self.withdraw()
# Position the menu under the mouse
x = self.parent.winfo_pointerx()
y = self.parent.winfo_pointery()
self.geometry("+%d+%d" % (x, y))
# Configure the window without borders
self.update_idletasks() # Mandatory for .overrideredirect() method
self.overrideredirect(True)
# Binding to close the menu if user does something else
self.bind("<FocusOut>", self.close) # User focus on another window
self.bind("<Escape>", self.close) # User press Escape
self.protocol("WM_DELETE_WINDOW", self.close)
# The configuration items
w = Tix.Checkbutton(self, text="Config item")
w.pack()
# Show the window
self.deiconify()
def close(self, event=None):
self.parent.focus_set()
self.destroy()
tixRoot = Tix.Tk()
tixRoot.withdraw()
app = MainWindow(tixRoot)
app.mainloop()

The other solutions didn't work for me when I had 2 <tkinter.Entry>s but I found this:
import tkinter as tk
focus = 0
def focus_in(event):
global focus
focus += 1
def focus_out(event):
global focus
focus -= 1
if focus == 0:
root.destroy()
root = tk.Toplevel()
root.bind("<FocusIn>", focus_in)
root.bind("<FocusOut>", focus_out)
root.mainloop()
It has a counter for the number of times the window is focused. It destroys the window when the counter reaches 0.

Related

How to pause the calling function that opens a child window until the child window is closed

I have a (hopefully) simple tkinter question, but in pouring over stack overflow, google, and other sites, all my searches seem to result in answers on multithreading which I don't think is the issue here.
Basically when I open a child window from a function in the main window, that calling function continues to execute, but I want it to pause when the child window is opened and resume when it's destroyed. Much like in "normal" (ie not tkinter) code, one function call executes and returns before the rest of the code is executed.
This is the essence of the code:
class ChildWindow(self, mainwin):
def __init__(self):
# build child window with tk.Toplevel(mainwin)
# Get input from entry box
# destroy()
class myGUI:
def __init__(self):
# Typical window setup stuff - canvas and buttons etc
def canvas_clicked(self):
# get data from canvas
ChildWindow()
print('This prints whilst the child window is still open')
print('But I want the Child Window to close before anything below the ChildWindow() call is executed')
print('Basically I want to print to screen what is put into the entry box in the Child Window')
Is this possible? My solution at the moment is to put those print statements in another routine which I call from from the child window right before ChildWindow.destroy() but this seems clunky - I'm sure there's a more elegant way without getting into complex multithreading stuff....
thanks
You can use wait_window method to pause the execution of the main window while the child window is open. Here is an example of how to modify your code:
class ChildWindow:
def init(self, master):
self.master = master
self.top = tk.Toplevel(master)
self.entry = tk.Entry(self.top)
self.entry.pack()
self.button = tk.Button(self.top, text='OK', command=self.close)
self.button.pack()
self.top.wait_window()
def close(self):
self.top.destroy()
class myGUI:
def init(self, master):
self.master = master
self.canvas = tk.Canvas(self.master)
self.canvas.pack()
self.canvas.bind('<Button-1>', self.canvas_clicked)
def canvas_clicked(self, event):
ChildWindow(self.master)
print('The text in the entry box is:', ChildWindow.entry.get())
root = tk.Tk()
app = myGUI(root)
root.mainloop()
The wait_window method will block the execution of the main window until the child window is destroyed. Once the child window is destroyed, the execution of the main window will resume and the print statement will display the text entered in the child window. Note that you need to pass the master argument (i.e., the root window) to the ChildWindow class to create the child window as a Toplevel widget.

python 3.8 tkinter calling new window from method creates a blank window

I am trying to call a new window by pushing the button of an existing window. The original window should close when the new window is being created. When I push the button the new window will show up as expected but additionally a blank window will appear. Using tk.Tk() or tk.Toplevel() will lead to the same result.
Later in the program I want to destroy the created window again. When using tk.Tk() closing the blank window by mouse will create an error "application has been destroyed" when the destroy() method gets applicated to the new window.
import tkinter as tk
from tkinter import messagebox
def main():
root = tk.Tk()
root.title("Hauptmenü")
Menue = MainMenue(root)
Menue.pack()
root.mainloop()
class MainMenue(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.button_rennen = tk.Button(self, text="New Window", width=20, command=self.call_bet)
self.button_rennen.pack()
def call_bet(self):
self.destroy()
root2 = tk.Tk()
Bet = BetFrame(root2)
Bet.pack()
class BetFrame(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.button = tk.Button(text="Wette platzieren",
command=self.question)
self.button.pack()
def question(self):
dialog = tk.messagebox.askokcancel(message="Destroy Window?")
if dialog is True:
self.destroy()
main()
I am creating a new class for every new window since the original program should return some variables.
I know that there are already many questions to this topic but for me none of these seemed quite to fit and helped to find the solution for my problem. I am grateful for any help!
Look at this:
import tkinter as tk
from tkinter import messagebox
class MainMenue(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.button_rennen = tk.Button(self, text="New Window", width=20,
command=self.call_bet)
self.button_rennen.pack()
def call_bet(self):
# `self.master` is the window
Bet = BetFrame(self.master)
Bet.pack()
self.destroy()
class BetFrame(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
# You need to tell the button that its master should be this BetFrame
# If you don't it will assume you mean the window so
# `self.destroy()` isn't going to destroy the button.
self.button = tk.Button(self, text="Wette platzieren", command=self.question)
self.button.pack()
def question(self):
dialog = tk.messagebox.askokcancel(message="Destroy Window?")
if dialog is True:
self.destroy()
def main():
root = tk.Tk()
Menue = MainMenue(root)
Menue.pack()
root.mainloop()
main()
Your code is great but it was 2 mistakes:
Instead of creating a new window is it better to reuse the old one.
When you create your button in your BetFrame class, you don't pass anything for the master argument (the first argument). That is why the button isn't destroyed when you destroy the BetFrame object using self.destroy()

Tkinter - My custom icon "disturbs" the Top Level window when it opens

when I opened my Top Level window, I always saw an annoying and weird behaviour.. at the end I realized that it was because of my custom icon.
below an exaple code:
from tkinter import *
from tkinter import ttk
class MainWindow:
def __init__(self):
self.parent=Tk()
self.parent.geometry("494x410+370+100")
self.parent.title("My Software - WITH ICON")
self.parent.iconbitmap("icon.ico")
Button = ttk.Button(self.parent, text="open a new widnow", command=self.OpenNewWindow)
Button.place(x=16, y=16)
def OpenNewWindow(self):
self.obj = NewWindow(self)
class NewWindow:
def __init__(self, mw):
self.window, self.mw = Toplevel(mw.parent), mw
self.window.geometry("200x150+360+200")
self.window.title("New Window")
self.window.iconbitmap("icon.ico") # it creates the issue..
self.window.protocol("WM_DELETE_WINDOW", self.on_close)
self.window.focus()
self.mw.parent.attributes('-disabled', 1)
self.window.transient(mw.parent)
self.window.grab_set()
self.mw.parent.wait_window(self.window)
def on_close(self):
self.mw.parent.attributes('-disabled', 0)
self.window.destroy()
def main():
app=MainWindow()
app.parent.mainloop()
if __name__=="__main__":
main()
to make it clear where is the issue, I create a GIF:
here we have two softwares, "without icon.py" and "with icon.py". they are the same, but the first one doesn't use my custom icon for his second window.
as you can see, if I run "with icon.py" the second window will be always affected by something when I will open it, but for "without icon.py" this "something" doesn't exist.
what is the issue? the software opens his second window, focus the root one (it's the issue), and then focus the second window again. you can see it clearly from the GIF.
how can I solve the issue? and why with the default icon this weird behaviour doesn't happen?

Tk/Tkinter: Detect application lost focus

I am making a Tkinter application. In the application, I want to pop up a context menu, which I do using Tk.Menu.post().
I don't know how to make this menu disappear when the application loses focus. I need to do this because the menu stays on top, even when switching to another window, leaving a menu 'artifact'.
I put a <FocusOut> event on the menu, which is triggered if the menu has focus and the user moves focus to another application. This works well.
What do I do if the main application window has focus? I could put a <FocusOut> event on the application window, which closes the menu; however, this ends up being called when I give focus to the menu, which closes the menu. The menu is created with parent as the main application, so I am not sure why <FocusOut> on the main app is even triggered when the menu gets focus.
How do I distinguish between the main application window losing focus to a different application vs losing focus to my menu?
I don't want to use tk_popup() because I want the user to continue to provide input to the main window. (use of the menu is optional).
Thanks to #Brad Lanam I came up with a SOLUTION, which I have included:
from Tkinter import *
class App(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
self.entry = Entry(self)
self.entry.grid(padx=30, pady=30)
self.entry.focus_set()
self.entry.bind("<Tab>", self.put_menu)
self.entry.bind("<Down>", self.set_focus_on_menu)
self.menu = Menu(self, tearoff=False)
self.menu.add_command(label="test")
self.menu.bind("<FocusOut>", self.destroy_menu)
self.bind("<FocusIn>", self.app_got_focus)
self.bind("<FocusOut>", self.app_lost_focus)
self.bind("<3>", self.put_menu)
def app_got_focus(self, event):
self.config(background="red")
def app_lost_focus(self, event):
self.config(background="grey")
######## SOLUTION BEGIN #########
if self.focus_get() != self.menu:
self.destroy_menu(event)
######## SOLUTION END ###########
def set_focus_on_menu(self, event):
self.menu.focus_set()
def menu_got_focus(self, event):
self.menu.activate(0)
def put_menu(self, event):
self.menu.post(self.winfo_x() + self.entry.winfo_x(), self.winfo_y() + self.entry.winfo_y()+20)
def destroy_menu(self, event):
self.menu.destroy()
app = App()
app.mainloop()
self.focus_get() will return the object that has focus, which can be used to distinguish between the menu receiving focus, vs some other application.
For example, to remove the menu when the focus moves to another application:
def app_lost_focus(self, event):
if self.focus_get() != self.menu:
self.destroy_menu(event)

How to close Toplevel window after the function it calls completes?

Edit:
let me include my code so I can get some specific help.
import Tkinter
def goPush():
win2=Tkinter.Toplevel()
win2.geometry('400x50')
Tkinter.Label(win2,text="If you have prepared as Help describes select Go otherwise select Go Back").pack()
Tkinter.Button(win2,text="Go",command=bounceProg).pack(side=Tkinter.RIGHT,padx=5)
Tkinter.Button(win2, text="Go Back", command=win2.destroy).pack(side=Tkinter.RIGHT)
def bounceProg():
d=1
print d
root=Tkinter.Tk()
root.geometry('500x100')
Tkinter.Button(text='Go', command=goPush).pack(side=Tkinter.RIGHT,ipadx=50)
root.mainloop()
So when you run the program it opens a window that says Go. Then Go opens a window that asks if youve read the help(which I didnt include in this code sample) and offers Go Back(which goes back) and Go. When you select Go it calls a function which prints 1. After it prints 1 I want the Window to close returning to the original window containing the Go button. How do I do such a thing?
#Kosig It won't quit root. Ie. self.foo = tk.Toplevel(self) and then self.foo.destroy()
For example:
class Foo(tk.Frame):
"""Foo example"""
def __init__(self, master=None):
"""Draw Foo GUI"""
tk.Frame.__init__(self, master)
self.grid()
self.draw_window_bar()
def draw_window_bar(self):
"""Draw bar TopLevel window"""
self.window_bar = tk.Toplevel(self)
# Some uber-pythonian code here...
ask_yes_or_no = messagebox.askyesno('Brian?', 'Romani Ite Domum')
if not ask_yes_or_no:
self.window_bar.destroy()
You have one main object, which is Foo. Foo has one main window (called "frame"), which it gets from tk.Frame. Afterwards, all Toplevel windows (frames) must be created within it. So, your new window here is self.window_bar and all its "objects" are in there, including the method for destroying it (self.window_bar.destroy()). You can call self.window_bar.destroy() from any part of the code, but here it is called after the user clicks "no".
If you create a toplevel window with the Toplevel command, you destroy it with the destroy method of the window object. For example:
import Tkinter as tk
class MyToplevel(tk.Toplevel):
def __init__(self, title="hello, world", command=None):
tk.Toplevel.__init__(self)
self.wm_title(title)
button = tk.Button(self, text="OK", command=lambda toplevel=self: command(toplevel))
button.pack()
if __name__ == "__main__":
def go(top):
print "my work here is done"
top.destroy()
app = tk.Tk()
t = MyToplevel(command=go)
t.wm_deiconify()
app.mainloop()
Apparently you just call quit on the root object that's running your mainloop
edit: All Tkinter widgets have a destroy() method which destroys that widget and its children. So you should be able to call this on your Toplevel

Categories