You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现Tkinter窗口自动隐藏/显示?游戏准星开发报错求助

问题:Apex Legends自定义准星工具开发报错及修复方案

需求背景

为《Apex Legends》制作自动显示/隐藏的屏幕准星,要求在返回桌面、最小化游戏或切换浏览器(游戏无边框运行)时自动消失,使用Python+Tkinter开发。

原代码及报错

原代码

import time
from tkinter import *

class CrosshairWindow:
    def __init__(self):
        self.root = Toplevel()
        self.root.attributes(
            '-transparentcolor', '#ffffff',
            '-topmost', '1'
        )
        self.root.overrideredirect(1)

        self.screen_width = self.root.winfo_screenwidth()
        self.screen_height = self.root.winfo_screenheight()

    def start(self):
        image = PhotoImage(file='crosshair.png')

        canvas = Canvas(self.root, width=image.width(), height=image.height(), highlightthickness=0)
        canvas.pack(fill='both')
        canvas.create_image(0, 0, anchor=NW, image=image)

        self.root.geometry(f'%sx%s+%s+%s' % (
            image.width(),  # width
            image.height(),  # height
            round(self.screen_width/2 - image.width()/2),  # x offset
            round(self.screen_height/2 - image.height()/2),  # y offset
        ))

        self.root.lift()
        self.root.mainloop()

if __name__ == '__main__':
    import win32gui as w
    print('Started!')
    while(True):
        title = w.GetWindowText(w.GetForegroundWindow())
        print(title)
        cw = CrosshairWindow()
        while 1:
            time.sleep(1)
            print(title)
            title = w.GetWindowText(w.GetForegroundWindow())
            if title == "Apex Legends":
                key = True
            else:
                key = False
            print(title)
            if key==True:
                cw.start()
            else:
                cw.root.destroy()

报错信息

C:\ProgramData\Anaconda3\python.exe "C:/Users/Hosisora_Ling/Documents/Tencent Files/2374416274/FileRecv/crosshair.py"
Started!
新建文件夹 – C:\Users\Hosisora_Ling\Documents\Tencent Files\2374416274\FileRecv\crosshair.py
Traceback (most recent call last):
  File "C:\Users\Hosisora_Ling\Documents\Tencent Files\2374416274\FileRecv\crosshair.py", line 41, in <module>
    cw.start(0)
  File "C:\Users\Hosisora_Ling\Documents\Tencent Files\2374416274\FileRecv\crosshair.py", line 33, in start
    self.root.lift()
  File "C:\ProgramData\Anaconda3\lib\tkinter\__init__.py", line 1026, in tkraise
    self.tk.call('raise', self._w, aboveThis)
_tkinter.TclError: can't invoke "raise" command: application has been destroyed

进程已结束,退出代码1#program ended with exit code 1

错误分析

  1. 循环逻辑混乱:外层while(True)每次循环都新建CrosshairWindow实例,内层循环又重复执行窗口显示/销毁操作,导致窗口被销毁后,后续代码仍尝试调用已失效的实例方法。
  2. Tkinter窗口使用错误:使用Toplevel作为主窗口(Toplevel依赖主Tk()实例),且mainloop()会阻塞线程,不能在循环中反复调用,否则会导致线程阻塞和窗口状态混乱。
  3. 窗口销毁后重复操作:当游戏不是前台窗口时销毁了cw.root,但切回游戏时仍使用同一个已销毁的实例调用start(),引发"application has been destroyed"错误。
  4. 图片垃圾回收问题:PhotoImage在start()函数内定义,函数执行完毕后会被Python垃圾回收,导致准星图片无法显示。

修复方案及代码

修复思路

  • 使用Tk()作为主窗口,避免Toplevel的依赖问题。
  • 用Tkinter的after()方法代替time.sleep()和死循环,实现非阻塞的前台窗口检测。
  • 只维护一个准星窗口实例,通过withdraw()和deiconify()控制显示/隐藏,而非反复销毁重建。
  • 将PhotoImage绑定到实例属性,避免被垃圾回收。

修复后的代码

import win32gui as w
from tkinter import Tk, Canvas, PhotoImage

class CrosshairWindow:
    def __init__(self):
        # 初始化主窗口
        self.root = Tk()
        self.root.attributes(
            '-transparentcolor', '#ffffff',
            '-topmost', True
        )
        self.root.overrideredirect(True)
        
        # 获取屏幕尺寸
        self.screen_width = self.root.winfo_screenwidth()
        self.screen_height = self.root.winfo_screenheight()
        
        # 加载准星图片并绑定为实例属性
        self.image = PhotoImage(file='crosshair.png')
        
        # 创建画布并添加图片
        self.canvas = Canvas(self.root, width=self.image.width(), height=self.image.height(), highlightthickness=0)
        self.canvas.pack(fill='both')
        self.canvas.create_image(0, 0, anchor='nw', image=self.image)
        
        # 定位准星到屏幕中心
        self.root.geometry(f'{self.image.width()}x{self.image.height()}+{round(self.screen_width/2 - self.image.width()/2)}+{round(self.screen_height/2 - self.image.height()/2)}')
        
        # 初始隐藏窗口
        self.root.withdraw()
        
        # 启动窗口检测循环
        self.check_foreground_window()
        
    def check_foreground_window(self):
        # 获取当前前台窗口标题
        window_title = w.GetWindowText(w.GetForegroundWindow())
        
        # 根据窗口标题控制准星显示/隐藏
        if window_title == "Apex Legends":
            if self.root.state() == 'withdrawn':
                self.root.deiconify()
                self.root.lift()
        else:
            if self.root.state() != 'withdrawn':
                self.root.withdraw()
        
        # 每隔500毫秒重复检测(可调整间隔)
        self.root.after(500, self.check_foreground_window)
        
    def start(self):
        self.root.mainloop()

if __name__ == '__main__':
    print('Started!')
    crosshair = CrosshairWindow()
    crosshair.start()

说明

  • 确保crosshair.png和脚本在同一目录下,或修改图片路径为绝对路径。
  • 检测间隔设为500毫秒,可根据需求调整after()的第一个参数(单位:毫秒)。
  • 窗口始终保持置顶(-topmost),确保在游戏窗口上方显示。

内容的提问来源于stack exchange,提问作者星空凌

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 23:25:24