如何检测 window 是否在其他 windows 前面

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

有没有办法在 parent 聚焦时把 child window 放在前面?我要解决的问题是:我有一个 parent window (root) 和一个 child window (TopLevel) 称为“菜单”。 child window 是一个浮动菜单,上面有几个按钮,titlebar-less。

如果我设置 menu.wm_attributes('-topmost', 1) 而不是 child window 所谓的“菜单”始终保持在顶部,即使我打开另一个应用程序菜单停留在最上面windows,不太实用

如果我重置 menu.wm_attributes('-topmost', 0) 并且我关注 parent window,child window 保持不变在所有其他 windows 之后,我看不到它。如果我是 运行 我的应用程序,然后必须打开另一个应用程序(例如 Dbeaver 或 Firefox),就会发生这种情况。然后我将我的应用程序放在前面,但 child 留在 Dbeaver 或 Firefox 后面。

我想做的是检测主要 window 何时聚焦,这样我就可以将 child window 放在前面,这样根和顶层在前面。

我在网上做了一些广泛的搜索。发现了很多关于检测 window 是否打开或关闭的信息,但没有关于检测 windows 是否打开的信息。

我使用 python 3.8 和 tkinter。

这是我到目前为止在该部分代码中的内容。还不能很好地工作,但已经很接近了:

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)

在 Matiiss 的建议下,我获得了以下代码以用于 Windows,而不是 Linux。所以我结合了我的 Linux 版本和他的 Windows 版本,以根据所使用的操作系统工作。还有一个新版本使用的代码更少,如已接受的答案所示。

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()

这个没有边框的浮动 windows 看起来很酷。它允许我使用完整的父级 window 来显示数据库信息,并将按钮(功能)放在浮动菜单上。

@acw1668 提到了这个更简单的解决方案,即使用 .transient(我今天发现):

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)

所以我想出了一个完全不同的方法(它至少适用于 windows),如果 Toplevel window 有 overrideredirect标志设置为 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()

我最终使用了 Matiiss 和 acw1668 解决方案来使这个 menu/toolbar 与 Microsoft Windows 和 Linux 兼容。在下面的第二个版本中,我添加了更多代码来展示 menu/toolbar 的样子。在顶部打开另一个应用程序时,parent 和 child 都移到后面,再次选择 parent 时,return 都移到前面。 acw1668'方案减少了代码量

简单版:

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()

在浮动菜单上显示最终大小和按钮的较长版本:

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()