What is the proper way to apply style in ttkbootstrap?

818 Views Asked by At

I'm creating software using tkinter and ttkbootstrap and on that software, users have to log in first. If they log in they will redirect to the Home window. But when a user logged in and redirected to the Home window the style of the Home window not working. Specifically, the bootstyle is not working. But the style of the Home window works when I run it only. This means if I run the home.py the style works perfectly. But, if I show the home window after destroying the login window it's not working.

My main.py code:

import ttkbootstrap as ttk
from orm.window.loginwindow import LoginWindow
from orm.home.home import Home
from database.internet import have_internet
from ttkbootstrap.dialogs import Messagebox

if __name__ == "__main__":
    app = LoginWindow()
    # app = Home()
    print(app)
    app.mainloop()

My log in window code:

import ttkbootstrap as ttk
from ttkbootstrap.constants import *
from ttkbootstrap.dialogs import Messagebox
from database.auth import login 
from orm.home.home import Home
from database.internet import have_internet

import sys, os
sys.path.insert(0, os.path.abspath('..'))

class LoginWindow(ttk.Window):
    def __init__(self):
        super().__init__(themename="superhero")

        self.title("MidGen (Login)")
        self.geometry("750x550")

        # username entry
        ttk.Label(self, text="Username: ").place(relx=0.3, rely=0.3)
        self.username = ttk.Entry(self)
        self.username.configure(bootstyle="success", width=40)
        self.username.place(relx=0.3, rely=0.35)

        # password entry
        ttk.Label(self, text="Password: ").place(relx=0.3, rely=0.45)
        self.password = ttk.Entry(self)
        self.password.configure(bootstyle="success", width=40, show="*")
        self.password.place(relx=0.3, rely=0.50)

        # show pass or not
        self.show_or_not = ttk.IntVar()
        pass_check = ttk.Checkbutton(
            self, bootstyle="primary",
            text="Show password",
            variable=self.show_or_not,
            onvalue=1,
            offvalue=0,
            command=lambda: self.password.configure(show="") if self.show_or_not.get() == 1 else self.password.configure(show="*"))
        pass_check.place(relx=0.3, rely=0.60)

        # login button
        login_btn = ttk.Button(self)
        login_btn.configure(bootstyle="success", text="Login", width=40, command=self.login)
        login_btn.place(relx=0.3, rely=0.67)
        self.resizable(False, False)

    def login(self):
        un = self.username.get()
        ps = self.password.get()
        if not have_internet():
            Messagebox.show_error("Check your internet connection", "Error")
            return
        data = login(un, ps)
        print(data["status"])
        if data["status"] == 200:
            self.destroy()
            home_view = Home()
            style = ttk.Style("darkly")
            home_view.mainloop()
        else:
            Messagebox.show_error("Username not found or wrong password\nTry again!", "Error")

My home window code:

import ttkbootstrap as ttk
from ttkbootstrap.constants import *
import tkinter as tk
from ..common.menubar import Menubar
from ..frames.frames import Dashboard, Map


class Home(ttk.Window):
    def __init__(self):
        super().__init__(themename="superhero")
        self.title("MidGen")
        self.width = self.winfo_screenwidth()
        self.height = self.winfo_screenheight()
        self.geometry(f"{self.width}x{self.height}")
        

        menubar = Menubar(self)
        self.config(menu=menubar.menubar)

        
        # sidebar
        self.sidebar = ttk.Frame(self, bootstyle="success")
        self.sidebar.configure(bootstyle="success")
        self.sidebar.place(relx=0, rely=0, relwidth=0.2, relheight=1)

        self.brand_frame = ttk.Frame(self.sidebar, bootstyle="success")
        self.brand_frame.place(relx=0, rely=0, relwidth=1, relheight=0.15)

        bname = ttk.Label(self.brand_frame,
                          text="MidGen",
                          bootstyle="inverse-success",
                          font=("", 15, "bold"))
        bname.place(relx=.5, rely=.1, anchor="center")

        # sidebar button frame
        self.options = ttk.Frame(self.sidebar, bootstyle="success")
        self.options.place(relx=0, rely=0.05, relwidth=1, relheight=.2)

        # home button
        self.op1 = ttk.Button(self.options,
                              text="Dashboard",
                              style="TButton",
                              command=self.show_dashboard)
        self.op1.place(relx=0, rely=0, relwidth=1)
        self.op1['padding'] = (10, 10)

        # cursor pointer on hover
        self.op1.bind("<Enter>", self.on_button_enter)
        self.op1.bind("<Leave>", self.on_button_leave)

        # contacts button
        self.op2 = ttk.Button(self.options,
                              text="Contacts",
                              command=self.show_contacts)
        self.op2.place(relx=0, rely=.4, relwidth=1)
        self.op2['padding'] = (10, 10)

        self.op2.bind("<Enter>", lambda event: self.op2.config(cursor="hand2"))
        self.op2.bind("<Leave>", lambda event: self.op2.config(cursor=""))

        # map button
        self.op3 = ttk.Button(self.options,
                              text="Map",
                              command=lambda: self.map.tkraise())
        self.op3.place(relx=0, rely=.8, relwidth=1)
        self.op3['padding'] = (10, 10)

        self.op3.bind("<Enter>", lambda event: self.op3.config(cursor="hand2"))
        self.op3.bind("<Leave>", lambda event: self.op3.config(cursor=""))
         
        # dashboard
        self.dashboard = ttk.Frame(self, bootstyle="danger")
        self.dashboard.place(relx=.2, rely=0, relwidth=.8, relheight=1)

        self.maindash = Dashboard(self.dashboard)
        self.maindash.place(relx=0, rely=0)

        # contacts
        self.contacts = ttk.Frame(self, bootstyle="warning")
        self.contacts.place(relx=.2, rely=0, relwidth=.8, relheight=1)

        # map 
        self.map = ttk.Frame(self, bootstyle="dark")
        self.map.place(relx=.2, rely=0, relwidth=.8, relheight=1)

        self.mainmap = Map(self.map)
        self.mainmap.place(relx=0, rely=0, relwidth=1, relheight=1)

        self.dashboard.tkraise()


    def show_dashboard(self):
        self.dashboard.tkraise()

    def show_contacts(self):
        self.contacts.tkraise()
        

    def on_button_enter(self, event):
        self.op1.config(cursor="hand2")  # Change cursor to pointer on hover

    def on_button_leave(self, event):
        self.op1.config(cursor="")

I tried to apply the theme many ways but none of them worked.

1

There are 1 best solutions below

0
On

Since ttkbootstrap.Style class uses single instance logic, so it uses a class variable instance to store the reference of the first created instance.

ttkbootstrap.Style

class Style(ttk.Style):
    instance = None         # class variable to store the instance of class
    ...

When LoginWindow is destroyed, the instance of Style created inside LoginWindow is also destroyed but the class variable Style.instance still refers to the destroyed instance. So subsequent functions called on the style will raise exception.

You need to reset the class variable instance to None after destroying LoginWindow:

class LoginWindow(ttk.Window):
    ...

    def login(self):
        ...
        if data["status"] == 200:
            self.destroy()
            ttk.Style.instance = None    # reset Style.instance to None
            home_view = Home()
            home_view.style.theme_use("darkly") # select theme
        else:
            ...