Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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()
like image 776
jgoeders Avatar asked Oct 22 '22 03:10

jgoeders


1 Answers

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)
    
like image 117
jgoeders Avatar answered Oct 24 '22 03:10

jgoeders