如何在Tkinter中实现完全可穿透点击的多显示器水印窗口?
如何在Tkinter中实现完全可穿透点击的多显示器水印窗口?
我太懂这种烦恼了——想要一个完全不干扰操作的水印,既能实时刷系统信息,又得让鼠标点下面的软件一点不耽误。你之前试的那些方法要么还是拦点击,要么直接把窗口搞死没法更新,确实踩了不少坑。
问题的核心在于:哪怕你把Tk窗口弄成透明,文本所在的区域本质上还是窗口的一部分,系统依然会把鼠标事件发给它。要解决这个,得借助Windows的API修改窗口底层样式,让整个窗口(包括里面所有内容)彻底忽略鼠标操作。
下面是修正后的完整代码,我把关键细节给你掰碎了讲:
import socket import psutil import tkinter as tk from datetime import datetime from screeninfo import get_monitors import win32gui import win32con def set_clickthrough(hwnd): """把窗口设置为完全点击穿透模式""" try: # 获取当前窗口的扩展样式,避免直接覆盖原有配置 current_style = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE) # 添加点击穿透+分层透明的样式(用|=保留原有样式) new_style = current_style | win32con.WS_EX_LAYERED | win32con.WS_EX_TRANSPARENT win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, new_style) # 和Tk的-alpha保持一致的透明度(0.3对应76的alpha值) win32gui.SetLayeredWindowAttributes(hwnd, 0, 76, win32con.LWA_ALPHA) except Exception as e: print(f"设置点击穿透失败: {e}") def get_system_info(): """获取需要显示的系统信息""" hostname = socket.gethostname() # 兼容没有用户登录的极端情况 user = psutil.users()[0].name if psutil.users() else "Unknown User" current_time = datetime.now().strftime("%y-%m-%d %H:%M:%S") # 改成秒级更新更准确 return f"{hostname} | {user} | {current_time}" def display_watermark(): windows = [] # 给每个显示器单独创建水印窗口,适配多屏布局 for monitor in get_monitors(): root = tk.Tk() root.overrideredirect(True) # 去掉窗口边框和标题栏 root.attributes("-topmost", True) # 保持窗口永远在最上层 root.config(bg="#000000") # 设置黑色背景,用于后续透明处理 root.wm_attributes("-transparentcolor", "#000000") # 让黑色背景完全透明 # 窗口大小和位置完全匹配当前显示器 root.geometry(f"{monitor.width}x{monitor.height}+{monitor.x}+{monitor.y}") # 获取Tk窗口对应的Windows句柄,设置点击穿透 hwnd = root.winfo_id() set_clickthrough(hwnd) # 创建画布用来绘制水印文本 canvas = tk.Canvas(root, width=monitor.width, height=monitor.height, bg="#000000", highlightthickness=0) canvas.pack(fill="both", expand=True) windows.append((root, canvas, monitor)) def update_watermark(): """每秒更新一次水印内容""" new_text = get_system_info() for root, canvas, monitor in windows: canvas.delete("all") # 清除上一秒的旧文本 # 平铺绘制斜向水印,避免遮挡关键内容 for y in range(-50, monitor.height + 100, 400): for x in range(-100, monitor.width + 200, 800): canvas.create_text(x, y, text=new_text, font=("Arial", 28), fill="gray", angle=45) # 1秒后自动触发下一次更新 windows[0][0].after(1000, update_watermark) # 启动更新循环 update_watermark() # 统一管理所有窗口的事件循环(Tkinter只能有一个主循环,用这种方式兼容多窗口) while True: for root, _, _ in windows: root.update_idletasks() root.update() if __name__ == "__main__": display_watermark()
关键细节说明:
WS_EX_TRANSPARENT样式:这是实现点击穿透的核心——Windows会让设置了这个样式的窗口完全忽略所有鼠标/键盘事件,直接把事件传递给下面的窗口。WS_EX_LAYERED样式:必须和上面的样式一起用,它是窗口支持透明效果的前提,同时允许我们用API调整透明度。- 窗口句柄的正确获取:用
root.winfo_id()拿到Tk窗口对应的Windows系统句柄,才能调用win32gui的函数修改底层样式。 - 多屏适配:给每个显示器单独创建窗口,避免拼接屏幕时出现水印错位或遗漏的问题。
- 样式叠加而非覆盖:用
|=添加新样式,而不是直接赋值,这样不会丢失Tk窗口原本的必要配置。
为什么之前的方法没用?
-alpha只是让窗口半透明,但窗口本身还是会捕获鼠标事件;-transparentcolor只让指定颜色的区域透明,但文本所在区域依然属于窗口,还是会拦截点击;-disabled会禁用窗口所有交互,包括内部的更新逻辑,导致文本没法实时刷新;- 只设置
-topmost但没改底层样式,窗口还是会抢鼠标事件。
现在运行这个代码,你会发现水印既能正常每秒更新,鼠标点击完全能穿透到下面的应用,完美解决你的问题!
备注:内容来源于stack exchange,提问作者Aidar




