Tk/Tkinter: Detect application lost focus - python

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)

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.

Tkinter: How to deiconify a popup window (and give it focus)?

I am on Ubuntu 20.04 and struggling with making iconify() and deiconify() work for a popup window. Currently the popup window will not deiconify() after having been iconified. Here is a minimal example:
import tkinter as tk
class PopupWindow(tk.Toplevel):
""" Show summary
"""
def __init__(self, root):
super().__init__(master=root)
self.root = root
self.geometry("600x400")
self.title("Summary")
self.protocol("WM_DELETE_WINDOW", self.close_window)
button = tk.Button(
self, text="Close", command=self.close_window)
button.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
self.focus_force()
self.grab_set()
def close_window(self):
self.iconify()
#self.withdraw()
#self.transient()
#self.root.grab_set()
#self.root.focus_force()
class MainWindow(tk.Tk):
""" The main window
"""
def __init__(self):
tk.Tk.__init__(self)
self.popup_window = None
self.configure_window()
def open_popup_window(self):
""" Opens the popup window
"""
if self.popup_window is None:
self.popup_window = PopupWindow(self)
else:
self.popup_window.deiconify()
#self.popup_window.lift()
self.popup_window.focus_force()
#self.popup_window.grab_set()
def configure_window(self):
""" Configure the main window
"""
self.geometry("600x400")
self.title("Cinema bookkeeper")
self.bind_all('<Control-w>', lambda e: self.destroy())
button = tk.Button(
self, text="Open popup window", command=self.open_popup_window)
button.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
def main():
window = MainWindow()
window.mainloop()
main()
When I click the "Open popup window" button a second time after it first has been iconified, nothing happens. Expected behavior was that the popup window would reappear and get focus. I can make it work if I use grab_set() but I would like not make the popup window modal such that both windows still can receive input at the same time.
The grab_set method routes all the events of the application to the specified widget.
Since you have called self.grab_set() on the popup window, the main window will refuse to take any event (the button is never clicked to begin with) and so the program doesn't work.
Removing that would make it work.

Tkinter Toplevel : Destroy window when not focused

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.

How to disable window controls when a modal dialog box is active in TkInter?

I am writing a Python application with TkInter. At some point the application (root) displays a dialog box (dlg, which is a Toplevel). In order to make the dialog modal I use the following code:
dlg.focus_set()
dlg.grab_set()
dlg.transient(root)
root.wait_window(dlg)
This indeed cancels "custom" events outside the dialog box (like the widgets in the main application window), but it does NOT cancel the window manager events, so that for example clicking on the main application window has it regain focus and it can be moved, resized - and even closed! - while the "modal" dialog is still open.
How can I make my dialog truly modal, so that window manager events for the main application window are also suspended while the dialog box is active?
I am using Python 3.4.3 on Ubuntu 15.04.
you can use root.grab_set_global() as in this exemple:
import Tkinter
class Application(Tkinter.Frame):
def mygrab(self):
print "grab is ok"
root.grab_set_global()
def createWidgets(self):
self.QUIT = Tkinter.Button(self)
self.QUIT["text"] = "QUIT"
self.QUIT["command"] = self.quit
self.QUIT.pack({"side": "left"})
self.grab = Tkinter.Button(self)
self.grab["text"] = "Grab",
self.grab["command"] = self.mygrab
self.grab.pack({"side": "left"})
def __init__(self, master=None):
Tkinter.Frame.__init__(self, master)
self.pack()
self.createWidgets()
root = Tkinter.Tk()
app = Application(master=root)
app.mainloop()
root.destroy()`
Try this way:
dlg.focus_set()
dlg.grab_set()
dlg.transient(root)
dlg.wait_window(dlg)

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