当形成新窗口时,tkinter 主题不起作用/中断

问题描述 投票:0回答:1

我正在开发一个小型 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可以工作,但从管理员登录调用它似乎破坏了主题。如果可以的话,请解释为什么会发生这种情况。

python tkinter ttkbootstrap
1个回答
0
投票

问题:

问题是您创建了

tk.Tk
Admin_Login
Dashboard
的两个实例。 这是两个根窗口。应该只有一个根窗口。 当您直接运行仪表板时,它会起作用,因为只创建了一个根窗口。

Tkinter 有一个叫做默认根的东西。创建的第一个 Tk 实例将成为所有对象(小部件/顶级窗口/样式/控制变量)的默认根,而无需显式父窗口。

ttb.Stylettk.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
© www.soinside.com 2019 - 2024. All rights reserved.