Search code examples
python-3.xtkinterfocus

How to detect if a window is in front of other windows


Is there a way to bring a child window in front when the parent is focused? What I am trying to resolve is this: I have a parent window (root) and a child window (TopLevel) called "menu." The child window is a floating menu with several buttons on it and is titlebar-less.

If I set menu.wm_attributes('-topmost', 1) than the child window called "menu" stays on top all the times, even if I open another app the menu stays above all windows, which is not very practical.

If I reset menu.wm_attributes('-topmost', 0) and that I focus on the parent window, the child window stays behind all other windows, and I cannot see it. This occurs if I am running my app and then have to open another app such as Dbeaver or Firefox. I then bring my app to the front but the child stays behind Dbeaver or Firefox.

What I am trying to do is detect when the main window is focused so that I can then bring the child window in the front so that both the root and the toplevel are in the front.

I did some extensive search on the net. Found a lot about detecting if the window is open or closed but nothing about detecting if the windows is open.

I use python 3.8 and tkinter.

Here is what I have so far in that section of the code. Doesn't work perfectly yet but it is very close:

def push_menu_front(event):
    focus = 0
    focus += 1
    if focus != 0:
        print("focus is 1")
        menu.wm_attributes('-topmost', 1)


def push_menu_back(event):
    focus = 0
    focus += 1
    if focus != 0:
        print("focus is 0")
        menu.wm_attributes('-topmost', 0)


root.bind("<FocusIn>", bring_menu_front)
root.bind("<FocusOut>", bring_menu_back)

Solution

  • I ended up using both Matiiss and acw1668 solutions to make this menu/toolbar compatible with both Microsoft Windows and Linux. In the second version below I added a bit more code to show how the menu/toolbar looks like. Both parent and child move to the back when opening another app on top, and both return to the front when selecting the parent again. acw1668' solution reduced the amount of code.

    Simple version:

    from tkinter import Tk, Toplevel, Button
    from sys import platform
    
    def start_side_menu():
        global menu
        menu = Toplevel(root)
        menu.geometry("90x300+620+123")
        menu.title("top")
        menu.resizable(False, False)
    
        # Remove borders in Windows
        if platform == "win32":
            menu.overrideredirect(1)
    
        # Remove borders in Linux + keep child with parent
        else:
            menu.wm_attributes('-type', 'splash')
            # acw1998 solution to keep the child with the parent with Linux
            menu.transient(root)
    
    def push_menu_front_win(event=None):
        # Matiiss solution to keep the child with the parent with Windows
        menu.attributes('-topmost', True)
        menu.attributes('-topmost', False)
    
    root = Tk()
    root.title('master')
    root.geometry("300x300+300+100")
    
    # Microsoft Windows OS call this function ----------
    
    if platform == "win32":
        root.bind("<FocusIn>", push_menu_front_win)
    
    start_side_menu()
    
    root.mainloop()
    

    Longer version to show final size and buttons on the floating menu:

    from tkinter import Tk, Toplevel, Button
    from sys import platform
    
    def start_side_menu():
        global menu
    
        menu = Toplevel(root)
        menu_width = 85
        menu_height = 377
        menu.title("")
        menu.resizable(False, False)
    
        # Remove borders in Windows
        if platform == "win32":
            r = (mon_width / 1) - (menu_width * 5.55)
            t = (mon_height / 2) - (menu_height / 1.2)
            menu.geometry(f'{menu_width}x{menu_height}+{int(r)}+{int(t)}')
            menu.overrideredirect(1)
    
        # Remove borders in Linux + keep child with parent
        else:
            r = (mon_width / 1) - (menu_width * 5.75)
            t = (mon_height / 2) - (menu_height / 1.5)
            menu.geometry(f'{menu_width}x{menu_height}+{int(r)}+{int(t)}')
            # acw1998 solution to keep the child with the parent with Linux
            menu.transient(root)
            menu.wm_attributes('-type', 'splash')
    
        # a couple of button just to show how to toolbar looks like
        search_checklist_btn = Button(menu, text='SEARCH', font=('FreeSans', 11), width=11, height=2, bg="#729FCF")
        search_checklist_btn.place(x=0, y=0, width=85)
        save_checklist_btn = Button(menu, text='NEW', font=('FreeSans', 11), width=11, height=2, bg="#729FCF")
        save_checklist_btn.place(x=0, y=47, width=85)
    
    def push_menu_front_win(event=None):
        # Matiiss solution to keep the child with the parent with Windows
        menu.attributes('-topmost', True)
        menu.attributes('-topmost', False)
    
    root = Tk()
    root.title('')
    root.geometry("920x980+500+20")
    mon_width = root.winfo_screenwidth()
    mon_height = root.winfo_screenheight()
    
    # For Microsoft Windows OS
    if platform == "win32":
        root.bind("<FocusIn>", push_menu_front_win)
    
    # start the menu function
    start_side_menu()
    
    root.mainloop()