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

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)

问题原因

  1. 多个Tk实例冲突:tkinter不支持同时创建多个Tk实例(Admin_Login和Dashboard均继承自tk.Tk),每个Tk对应独立的Tcl解释器,会导致样式、事件循环等全局资源混乱。
  2. 重复初始化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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 14:49:54