tkinter+ttkbootstrap主题在新窗口创建后失效问题求助
Ttkbootstrap主题在登录跳转后失效的问题原因分析
问题描述
开发基于tkinter(使用ttkbootstrap的darkly主题)的应用,流程为:先打开登录窗口,登录后进入主仪表板窗口。但出现异常:从登录窗口跳转打开仪表板时,主题失效/显示异常;直接启动仪表板窗口时,主题完全正常。
代码示例
main.py
from test_login import Admin_Login import ttkbootstrap as ttb if __name__ == "__main__": root = Admin_Login() style = ttb.Style("darkly") root.mainloop()
test_login.py(精简复现版)
import tkinter as tk import ttkbootstrap as ttb from dashboard import Dashboard class Admin_Login(tk.Tk): def __init__(self, *args, **kwargs): super().__init__() self.geometry('400x460') self.resizable(False, False) self.login_button = ttb.Button(self, text = "Login", width=8, command = self.call_dashboard) self.login_button.pack() def call_dashboard(self): self.withdraw() Dashboard()
dashboard.py(精简复现版)
from tkinter import Menu, Tk, Canvas from tkinter.ttk import Frame, Label import tkinter.ttk as ttk import ttkbootstrap as ttb from utils.toggle_menu import Toggle_Menu class Dashboard(Tk): def __init__(self, db=None): super().__init__() #configure Tk window self.title("My App") self.geometry('1920x1080') self.state('zoomed') #create masterframe that contains two columns style = ttb.Style('darkly') style.configure('custom1.TFrame', background='white') self.mainframe = Frame(self, style='custom1.TFrame') self.mainframe.rowconfigure(0, weight=1) self.mainframe.columnconfigure(0, minsize=200) self.mainframe.columnconfigure(1, weight=1) self.mainframe.pack(fill='both', expand=True) self.create_dashboard() def create_dashboard(self): #menu for dashboard self.menubar = Menu(self) self.file = Menu(self.menubar, background='white', activebackground='white') self.file.add_command(label ='New File\t\t\t', command = None, background='white', foreground='black') self.file.add_separator(background='white') self.file.add_command(label ='Exit', background='white', foreground='black', command = self.destroy) self.menubar.add_cascade(label='File', menu=self.file) #create left frame self.dashboard_options = Frame(self.mainframe, width=300, borderwidth=5) self.dashboard_title = Label(self.dashboard_options, text='Dashboard') self.dashboard = Canvas(self.mainframe) toggle1 = Toggle_Menu(self.dashboard_options) toggle1.add_option("child1") toggle1.add_option("child2") #pack widgets self.dashboard_title.pack(anchor='nw', fill='x', expand=False, padx=4) toggle1.pack(anchor='nw', fill='x', expand=True) self.dashboard_options.grid(row = 0, column=0, sticky='nsew', padx=(0,3)) self.dashboard.grid(row = 0, column=1, sticky='nsew') #set menu self.config(menu = self.menubar) if __name__ == "__main__": root = Dashboard() style = ttb.Style("darkly") root.mainloop()
toggle_menu.py(精简复现版)
import tkinter as tk import tkinter.ttk as ttk import ttkbootstrap as ttb from PIL import Image, ImageTk class Toggle_Menu(ttk.Frame): def __init__(self, parent, *args, **kwargs): ttk.Frame.__init__(self, parent, *args, **kwargs) #frames self.parent_frame = ttk.Frame(self, relief='flat') self.child_frame = tk.Canvas(self, relief='flat') #arrow label and parent name self.arrow_label = ttk.Label(self.parent_frame, image=None) self.parent_name = ttk.Label(self.parent_frame, text='parent') #bind all of them with left mouse click button self.parent_frame.bind('<Button-1>', self.toggle) self.arrow_label.bind('<Button-1>', self.toggle) self.parent_name.bind('<Button-1>', self.toggle) #widget packs self.arrow_label.pack(side='left', anchor='w', padx=(3,0), pady=10) self.parent_name.pack(side='left', anchor='w', padx=10, pady=10) self.parent_frame.pack(fill='x', expand=True, anchor='ne') self.child_frame.pack(fill='x', expand=True, anchor='ne') def toggle(self, event): ''' Toggles the parent frame on and off by unpacking/packing child frame ''' if self.child_frame in self.pack_slaves(): self.child_frame.pack_forget() else: self.child_frame.pack(fill='x', expand=True, anchor='ne') def add_option(self, button_name, command=None): ''' Add new options to child frame ''' style = ttb.Style('darkly') style.configure('info.Link.TButton', justify='left', foreground = 'white') button = ttk.Button(self.child_frame, text="hi", style="info.Link.TButton", command=None) button.pack(anchor='ne', fill='x', expand=True)
问题原因
- 多个Tk实例冲突:tkinter不支持同时创建多个
Tk实例(Admin_Login和Dashboard均继承自tk.Tk),每个Tk对应独立的Tcl解释器,会导致样式、事件循环等全局资源混乱。 - 重复初始化Style:ttkbootstrap的
Style是全局单例模式,第一次在main.py中初始化Style("darkly")后,后续在Dashboard和Toggle_Menu中再次调用ttb.Style('darkly')会破坏原有样式绑定——单例特性导致新的样式配置与旧实例冲突,最终表现为主题失效。
修复建议
方案1:使用Toplevel作为子窗口
将Dashboard改为继承自ttb.Window或tk.Toplevel,以登录窗口为父窗口,避免多Tk实例:
- 修改
test_login.py的call_dashboard方法:def call_dashboard(self): self.withdraw() Dashboard(self) # 传入登录窗口作为父窗口 - 修改
Dashboard类的定义:class Dashboard(ttb.Window): # 或 tk.Toplevel def __init__(self, parent, db=None): super().__init__(parent) # 绑定父窗口 # 其余代码保持不变
方案2:统一Style初始化
只在应用启动时初始化一次Style,后续模块直接使用全局实例,不再重复创建:
- 在
Dashboard和Toggle_Menu中替换原Style初始化代码:style = ttb.Style() # 获取全局已初始化的Style实例,无需再次指定主题
内容的提问来源于stack exchange,提问作者Hi Byee
相关产品推荐
相关产品推荐

