如何在tkinter中使用root.overrideredirect(True)控制焦点事件?
问题详情
- Python版本:3.12.3
- 系统:ArchLinux(装有未聚焦窗口透明合成器)
- 现象:
- tkinter的
<FocusOut>事件在普通窗口中可正常触发,但设置root.overrideredirect(True)后,窗口会保持置顶,无法接收/失去焦点,<FocusOut>/<FocusIn>事件完全失效 - 系统的窗口合成器无法识别该窗口,仿佛窗口不存在
- 尝试过
root.attributes('-type', 'splash'),窗口可以被其他窗口压下去,但焦点事件仍然无法触发
- tkinter的
需求
不取消root.overrideredirect(True)的前提下,实现:
- 移除窗口装饰
- 点击窗口外部时自动关闭窗口
示例代码
import tkinter as tk from pyautogui import position win_size: tuple = (100, 100) win_top_left: tuple = position() win_bottom_right: tuple = (win_top_left[0] + win_size[0], win_top_left[1] + win_size[1]) def destroy(*_) -> None: WIN.destroy() WIN: tk.Tk = tk.Tk() WIN.title("Window") WIN.geometry(f"{win_size[0]}x{win_size[1]}+{win_top_left[0]}+{win_top_left[1]}") WIN.overrideredirect(True) WIN.protocol("WM_DELETE_WINDOW", WIN.destroy) WIN.bind("<FocusOut>", destroy) # 仅移除WIN.overrideredirect(True)时生效 WIN.bind("<FocusIn>", destroy) # 同上 button: tk.Button = tk.Button(WIN, text="destroy", command=destroy) button.pack() WIN.mainloop()
解决方案
方案1:循环检测鼠标位置判断点击区域
利用tkinter的after方法定时检查鼠标位置,判断是否处于窗口区域外,满足条件则关闭窗口。该方法无需额外依赖,跨平台性较好。
修改后的代码:
import tkinter as tk from pyautogui import position def destroy(*_) -> None: WIN.destroy() def check_click_outside(): # 获取当前鼠标坐标 mouse_x, mouse_y = position() # 获取窗口的位置与尺寸 win_x = WIN.winfo_x() win_y = WIN.winfo_y() win_width = WIN.winfo_width() win_height = WIN.winfo_height() # 判断鼠标是否在窗口外部 if not (win_x <= mouse_x <= win_x + win_width and win_y <= mouse_y <= win_y + win_height): destroy() # 每100毫秒重复检测一次 WIN.after(100, check_click_outside) WIN: tk.Tk = tk.Tk() WIN.title("Window") win_size = (100, 100) WIN.geometry(f"{win_size[0]}x{win_size[1]}+{position()[0]}+{position()[1]}") WIN.overrideredirect(True) WIN.protocol("WM_DELETE_WINDOW", destroy) button: tk.Button = tk.Button(WIN, text="destroy", command=destroy) button.pack() # 启动检测循环 check_click_outside() WIN.mainloop()
方案2:Linux下用Xlib监听焦点变化(需额外依赖)
针对ArchLinux的X11环境,可通过python-xlib库直接监听系统窗口焦点事件,精准判断窗口是否失去焦点。
- 先安装依赖:
sudo pacman -S python-xlib
- 修改后的代码:
import tkinter as tk from pyautogui import position from Xlib import display, X import threading def destroy(*_) -> None: WIN.destroy() def check_focus(): dpy = display.Display() win_id = dpy.create_resource_object('window', WIN.winfo_id()) win_id.change_attributes(event_mask=X.FocusChangeMask) while True: event = dpy.next_event() if event.type == X.FocusOut: destroy() WIN: tk.Tk = tk.Tk() WIN.title("Window") win_size = (100, 100) WIN.geometry(f"{win_size[0]}x{win_size[1]}+{position()[0]}+{position()[1]}") WIN.overrideredirect(True) WIN.protocol("WM_DELETE_WINDOW", destroy) button: tk.Button = tk.Button(WIN, text="destroy", command=destroy) button.pack() # 启动Xlib焦点监听线程 threading.Thread(target=check_focus, daemon=True).start() WIN.mainloop()
方案3:取消置顶属性配合鼠标检测
如果不需要窗口一直置顶,可添加WIN.attributes('-topmost', False),结合方案1的鼠标位置检测,既能保留无装饰窗口,也能避免窗口霸占顶层。
内容的提问来源于stack exchange,提问作者Ayman Al-mankkadhi
相关产品推荐
相关产品推荐

