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()
关键修改点
- 使用
icon.run_detached():替代icon.run(),让托盘在后台持续运行,无需重复启动/停止。 - 调整
open_app逻辑:直接调用app.deiconify()显示窗口,避免停止托盘导致资源释放。 - 简化窗口关闭逻辑:隐藏窗口时仅执行
app.withdraw(),不再重复启动托盘。 - 统一销毁流程:退出时先停止托盘再销毁窗口,确保资源正常回收。
内容的提问来源于stack exchange,提问作者AshhadDevLab
相关产品推荐
相关产品推荐

