Reputation: 349
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)
Upvotes: 0
Views: 925
Reputation: 349
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()
Upvotes: 0
Reputation: 349
With Matiiss' suggestions, I got the following code to work on Windows, not Linux. So I have combined my version of Linux and his version of Windows to work based on the operating system used. There is also a new version that uses a bit less code as seen in accepted answer.
from tkinter import Tk, Toplevel
from sys import platform
def push_menu_front_win(event=None):
menu.attributes('-topmost', True)
menu.attributes('-topmost', False)
def push_menu_front(event=None):
menu.attributes('-topmost', True)
def push_menu_back(event=None):
menu.attributes('-topmost', False)
root = Tk()
root.title('master')
root.geometry("300x300+300+100")
menu = Toplevel(root)
menu.title('top')
menu.geometry("120x300+610+100")
# --------------- Select the OS used with if statement ----------
# Microsoft Windows OS
if platform == "win32":
root.bind("<FocusIn>", push_menu_front_win)
# MAC OS
elif platform == "darwin":
root.bind("<FocusIn>", push_menu_front)
root.bind("<FocusOut>", push_menu_back)
# Linux OS
elif platform == "linux" or platform == "linux2":
root.bind("<FocusIn>", push_menu_front)
root.bind("<FocusOut>", push_menu_back)
root.mainloop()
This floating windows without border looks pretty cool. It allows me to use the full parent window to display database information, and place the buttons (functions) on the floating menu.
Upvotes: 1
Reputation: 6156
So I came up with a completely different way of doing this (it works on windows at least), this works if the Toplevel
window has overrideredirect
flag set to True
:
from tkinter import Tk, Toplevel, Label, Entry
root = Tk()
root.title('master')
root.bind('<FocusIn>', lambda _: top.deiconify())
top = Toplevel(root)
top.title('top')
top.overrideredirect(True)
root.mainloop()
Upvotes: 1
Reputation: 6156
So there is this much easier solution, mentioned by @acw1668, that is to use .transient
(which I found out about today):
from tkinter import Tk, Toplevel
root = Tk()
root.title('master')
top = Toplevel(root)
top.title('top')
top.transient(root)
root.mainloop()
.transient
docs (more of a definiton)
Upvotes: 1