使用sv_ttk库为多个Tkinter窗口设置主题时触发_Tkinter.TclError错误
我完全明白你遇到的困扰——单独给主设置窗口或者兵升变弹窗用sv_ttk暗色主题都没问题,但同时给两个窗口设置就会弹出_tkinter.TclError: can't invoke "winfo" command: application has been destroyed的错误,这确实挺让人头疼的。咱们来拆解问题根源,再给出对应的解决办法。
问题根源
首先得搞清楚sv_ttk的主题设置逻辑:sv_ttk.use_dark_theme()本质是调用set_theme("dark"),而这个set_theme函数默认会用tkinter._get_default_root()获取第一个创建的Tk根窗口作为操作对象。
当你创建第二个Tk()实例(也就是那个兵升变弹窗),然后调用use_dark_theme()时,它依然会尝试去操作第一个根窗口(主设置窗口)。如果此时主窗口已经被关闭销毁,或者两个独立的Tcl解释器实例(每个Tk()对应一个)发生冲突,就会触发这个“应用已销毁”的错误。
另外还要提一句:Tkinter本身就不推荐创建多个Tk()实例,因为每个实例对应独立的Tcl解释器,很容易引发主题、资源共享这类奇怪的问题。
解决方案
这里给你两个方案,优先推荐第一个,更符合Tkinter的设计规范:
方案1:把弹窗改成Toplevel(最推荐)
把弹窗从独立的Tk()改成主窗口的Toplevel子窗口,这样所有窗口共享同一个Tcl解释器,主题只需设置一次就会自动应用到所有子窗口,不需要重复调用sv_ttk的主题函数。
修改后的弹窗代码大概是这样:
def askPawnPromotion(main_root): # 传入主窗口的实例 """ Ask the player which piece to promote the pawn to. This function uses a Tkinter window to prompt the player for a piece to promote the pawn to. """ def apply_selection(): global PROMOTION_PIECE PROMOTION_PIECE = promotion_combo.get() popup.destroy() global PROMOTION_PIECE # 用Toplevel代替Tk(),绑定到主窗口 popup = t.Toplevel(main_root) ntkutils.dark_title_bar(popup) popup.title("Good Chess | Pawn Promotion") popup.iconbitmap("images/GAME/icon.ico") window_width = 350 window_height = 200 screen_width = popup.winfo_screenwidth() screen_height = popup.winfo_screenheight() x_position = (screen_width - window_width) // 2 y_position = (screen_height - window_height) // 2 popup.geometry(f"{window_width}x{window_height}+{x_position}+{y_position}") promotion_label = ttk.Label(popup, text="Choose a piece to promote the pawn to:") promotion_combo = ttk.Combobox(popup, values=["Queen", "Rook", "Bishop", "Knight"]) promotion_combo.set("Queen") apply_button = ttk.Button(popup, text="APPLY", command=apply_selection) promotion_label.pack(pady=10) promotion_combo.pack(pady=10) apply_button.pack(pady=20) # 这里不需要再调用sv_ttk的主题函数,会自动继承主窗口的主题 popup.mainloop() return PROMOTION_PIECE[0]
然后主窗口里只需要调用一次sv_ttk.use_dark_theme()就够了,弹窗会自动用上这个主题。
方案2:强制指定主题应用的窗口(适合必须保留多Tk实例的场景)
如果因为某些特殊原因,你必须保留两个独立的Tk()实例,那可以在调用主题设置时,明确指定要应用的窗口实例,而不是依赖默认的根窗口:
- 主窗口里把
sv_ttk.use_dark_theme()改成:
sv_ttk.set_theme("dark", root=root)
- 弹窗里把
sv_ttk.use_dark_theme()改成:
sv_ttk.set_theme("dark", root=popup)
这样sv_ttk就会分别对指定的窗口应用主题,不会再去操作另一个窗口的资源。
额外提示
尽量避免创建多个Tk()实例,除了主题问题,还可能导致事件循环异常、资源无法共享等一系列潜在问题。如果主窗口关闭后弹窗还需要运行,用Toplevel的话可以修改主窗口的关闭协议(比如隐藏窗口而不是销毁),或者调整你的应用窗口层级结构。
备注:内容来源于stack exchange,提问作者Rares Olteanu

