我正在开发一个小型 tkinter(来自 ttkbootstrap 的主题)应用程序,它首先打开一个登录窗口,然后调用主仪表板窗口。 问题是使用的主题不起作用,或者从登录窗口调用时只是在仪表板中中断,但如果我直接打开仪表板,则工作完全正常。
代码:
主.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)
截图:
运行 main.py 会产生: 破碎
运行dashboard.py会产生: 预计
直接运行dashboard.py可以工作,但从管理员登录调用它似乎破坏了主题。如果可以的话,请解释为什么会发生这种情况。
问题是您创建了
tk.Tk
、Admin_Login
和 Dashboard
的两个实例。
这是两个根窗口。应该只有一个根窗口。
当您直接运行仪表板时,它会起作用,因为只创建了一个根窗口。
Tkinter 有一个叫做默认根的东西。创建的第一个 Tk 实例将成为所有对象(小部件/顶级窗口/样式/控制变量)的默认根,而无需显式父窗口。
ttb.Style 是 ttk.Style 的子类。允许在不传递父窗口的情况下创建该类的对象。
import tkinter as tk
import tkinter.ttk as ttk
root = tk.Tk()
root1 = tk.Tk()
style = ttk.Style()
# or
style1 = ttk.Style(root)
print(tk._default_root is root) # True
print(style.master is root) # True
print(style1.master is root) # True
ttb.Style
也是一个单例类,这意味着您不会在Dashboard
类中创建新实例,而是获取在main.py
中创建的相同对象。
当您第一次创建
ttb.Style
对象时,它与第一个根窗口 (Admin_Login
) 关联。
这就是为什么您的主题不适用于仪表板窗口子项。
当您直接运行仪表板时,主题会起作用,因为样式与 Dashboard(Tk)
相关联。
您通常只需要一个根窗口和该窗口的一个样式对象。 如果您需要多个附加窗口,可以使用 Toplevel 小部件。 顶级窗口及其所有子窗口将具有与根窗口相同的样式。如果您将样式创建为
Admin_Login
类 (self.style
) 的属性,那么它将可供 root 的子级 (self.master.style
) 使用。
对于 Toggle_Menu
类,这将是 self.master.master.master.master.style
。
测试登录.py
class Admin_Login(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__()
self.geometry('400x460')
self.resizable(False, False)
self.style = ttb.Style('darkly') # create a style only once
print("Admin_Login:", id(self), id(self.style))
...
def call_dashboard(self):
self.withdraw()
Dashboard(self) # explicitly pass the parent root
仪表板.py
from tkinter import Toplevel
class Dashboard(Toplevel):
def __init__(self, master, db=None):
super().__init__(master)
print("Dashboard:", id(self.master), id(self.master.style))
...
输出:
Admin_Login: 2170364988816 2170367009552
Dashboard: 2170364988816 2170367009552