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

CustomTkinter结合Pystray二次最小化触发WinError6问题求助

CustomTkinter + Pystray 二次隐藏窗口触发无效句柄错误

我用CustomTkinter替代Tkinter开发应用,需求是:当设置里的「最小化到托盘」复选框勾选时,关闭窗口时执行withdraw()隐藏窗口而非销毁。首次操作一切正常——窗口隐藏、托盘图标显示,点击托盘的「Open enigma:guard」按钮能恢复窗口;但第二次执行相同操作时,会触发OSError: [WinError 6] 无效句柄错误。


源代码

def save_checkbox_state(state):
    with open('.\_internal\src\checkbox_state.pickle', 'wb') as file:
        pickle.dump(state, file)

def read_checkbox_state():
    try:
        with open('.\_internal\src\checkbox_state.pickle', 'rb') as file:
            state = pickle.load(file)
            # 根据UI框架调整逻辑
            if state == 'on':
                checkbox.select()
                check_var.set('on')
            else:
                checkbox.deselect()
                check_var.set('off')
    except FileNotFoundError:
        return None
    
def app_destroy():
    icon.stop()
    app.destroy()

def open_app():
    icon.stop()
    app.after(0,app.deiconify())
    
image = Image.open(".\_internal\assets\ico.png")
menu=pystray.Menu(pystray.MenuItem("Open enigma:guard",open_app),pystray.MenuItem("Exit", app_destroy))
icon = pystray.Icon('enigma:guard', image, "My system tray icon", menu)

def destroy_window():
    checkbox_state = check_var.get()
    save_checkbox_state(checkbox_state)
    if checkbox_state == 'on':
        app.withdraw()
        
        icon.run()
    else:
        app.destroy()
        icon.stop()


settings_minimize_frame = customtkinter.CTkLabel(settings_frame_win,text="",image=settings_minimize)
settings_minimize_frame.place(x=420,y=121)

check_var = customtkinter.StringVar(value="off")
checkbox = customtkinter.CTkCheckBox(settings_minimize_frame, text="", variable=check_var, onvalue="on", offvalue="off",width=30,height=35,checkbox_height=35,checkbox_width=35,border_color="#1FFFA9",bg_color="#000000")
checkbox.place(x=1078,y=33)

app.protocol("WM_DELETE_WINDOW", destroy_window)

错误信息

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\[hidden]\AppData\Local\Programs\Python\Python311\Lib\tkinter\__init__.py", line 1948, in __call__
    return self.func(*args)
           ^^^^^^^^^^^^^^^^
  File "D:\python_encryptor_decryptor\project2\enigma_guard.py", line 1228, in destroy_window
    icon.run()
  File "C:\Users\[hidden]\AppData\Local\Programs\Python\Python311\Lib\site-packages\pystray\_base.py", line 212, in run
    self._run()
  File "C:\Users\[hidden]\AppData\Local\Programs\Python\Python311\Lib\site-packages\pystray\_win32.py", line 120, in _run
    self._hwnd = self._create_window(self._atom)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\[hidden]\AppData\Local\Programs\Python\Python311\Lib\site-packages\pystray\_win32.py", line 244, in _create_window
    hwnd = win32.CreateWindowEx(
           ^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\[hidden]\AppData\Local\Programs\Python\Python311\Lib\site-packages\pystray\_util\win32.py", line 204, in _err
    raise ctypes.WinError()
OSError: [WinError 6] The handle is invalid.

已尝试方案

  • 试过直接销毁窗口而非隐藏,但应用启动时会出现闪屏,用户体验太差,因此放弃该方案。

解决方案

问题根源是:第一次调用icon.run()后,icon对象的内部资源(如窗口句柄)已被释放,第二次再调用icon.run()就会因无效句柄报错。调整逻辑,让托盘图标持续后台运行,仅控制窗口的显示/隐藏即可解决问题。

修改后的代码

import pickle
import customtkinter
from PIL import Image
import pystray
from pystray import MenuItem as item

def save_checkbox_state(state):
    with open('.\_internal\src\checkbox_state.pickle', 'wb') as file:
        pickle.dump(state, file)

def read_checkbox_state():
    try:
        with open('.\_internal\src\checkbox_state.pickle', 'rb') as file:
            state = pickle.load(file)
            if state == 'on':
                checkbox.select()
                check_var.set('on')
            else:
                checkbox.deselect()
                check_var.set('off')
    except FileNotFoundError:
        return None

def app_destroy():
    # 先停止托盘再销毁窗口,确保资源正常释放
    icon.stop()
    app.destroy()

def open_app(icon, item):
    # 直接显示窗口,无需停止托盘
    app.deiconify()

def on_window_close():
    checkbox_state = check_var.get()
    save_checkbox_state(checkbox_state)
    if checkbox_state == 'on':
        # 仅隐藏窗口,托盘保持运行
        app.withdraw()
    else:
        app_destroy()

# 初始化应用主窗口
app = customtkinter.CTk()
# 假设settings_frame_win是已创建的框架
settings_frame_win = customtkinter.CTkFrame(app)
settings_frame_win.pack(fill="both", expand=True)

# 加载最小化图标(需确保路径正确)
settings_minimize = customtkinter.CTkImage(
    light_image=Image.open(".\_internal\assets\minimize_icon.png"),
    dark_image=Image.open(".\_internal\assets\minimize_icon.png")
)

settings_minimize_frame = customtkinter.CTkLabel(settings_frame_win, text="", image=settings_minimize)
settings_minimize_frame.place(x=420, y=121)

check_var = customtkinter.StringVar(value="off")
checkbox = customtkinter.CTkCheckBox(
    settings_minimize_frame, text="最小化到托盘",
    variable=check_var, onvalue="on", offvalue="off",
    width=30, height=35, checkbox_height=35, checkbox_width=35,
    border_color="#1FFFA9", bg_color="#000000"
)
checkbox.place(x=1078, y=33)

# 初始化托盘图标与菜单
image = Image.open(".\_internal\assets\ico.png")
menu = pystray.Menu(
    item("Open enigma:guard", open_app),
    item("Exit", app_destroy)
)
icon = pystray.Icon('enigma:guard', image, "enigma:guard", menu)

# 设置窗口关闭事件
app.protocol("WM_DELETE_WINDOW", on_window_close)

# 让托盘图标在后台运行,不阻塞Tkinter主循环
icon.run_detached()

# 启动应用主循环
app.mainloop()

关键修改点

  1. 使用icon.run_detached():替代icon.run(),让托盘在后台持续运行,无需重复启动/停止。
  2. 调整open_app逻辑:直接调用app.deiconify()显示窗口,避免停止托盘导致资源释放。
  3. 简化窗口关闭逻辑:隐藏窗口时仅执行app.withdraw(),不再重复启动托盘。
  4. 统一销毁流程:退出时先停止托盘再销毁窗口,确保资源正常回收。

内容的提问来源于stack exchange,提问作者AshhadDevLab

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 01:53:21