You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在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()

关键细节说明:

  1. WS_EX_TRANSPARENT样式:这是实现点击穿透的核心——Windows会让设置了这个样式的窗口完全忽略所有鼠标/键盘事件,直接把事件传递给下面的窗口。
  2. WS_EX_LAYERED样式:必须和上面的样式一起用,它是窗口支持透明效果的前提,同时允许我们用API调整透明度。
  3. 窗口句柄的正确获取:用root.winfo_id()拿到Tk窗口对应的Windows系统句柄,才能调用win32gui的函数修改底层样式。
  4. 多屏适配:给每个显示器单独创建窗口,避免拼接屏幕时出现水印错位或遗漏的问题。
  5. 样式叠加而非覆盖:用|=添加新样式,而不是直接赋值,这样不会丢失Tk窗口原本的必要配置。

为什么之前的方法没用?

  • -alpha只是让窗口半透明,但窗口本身还是会捕获鼠标事件;
  • -transparentcolor只让指定颜色的区域透明,但文本所在区域依然属于窗口,还是会拦截点击;
  • -disabled会禁用窗口所有交互,包括内部的更新逻辑,导致文本没法实时刷新;
  • 只设置-topmost但没改底层样式,窗口还是会抢鼠标事件。

现在运行这个代码,你会发现水印既能正常每秒更新,鼠标点击完全能穿透到下面的应用,完美解决你的问题!

备注:内容来源于stack exchange,提问作者Aidar

火山引擎 最新活动