Search code examples
pythontkintercomboboxthemes

how to fix combobox fill when changing theme


I'm trying to write an application with the ability to change the theme from dark to light. i use forest theme from rdbende. When loading only one theme via root.tk.call("source", "forest-dark.tcl"), everything is displayed correctly, but if after that another second theme is loaded root.tk.call("source", "forest-light.tcl"), the window background and combobox fill will be repainted in its color, even if it do not apply. (only apply style.theme_use("forest-dark")).

only dark theme add light theme

I tried to recolor comboboxes in the event function via root.option_add('*TCombobox*Listbox*Background', bg_color), but it only works the first time, and then it's just ignored.

Here is my code:

from tkinter import *
from tkinter import ttk


root = Tk()
root.geometry('550x500')

# Create a style
style = ttk.Style(root)

# Import the tcl file
root.tk.call("source", "forest-dark.tcl")
# Import the tcl file
root.tk.call("source", "forest-light.tcl")

dark_mode = True
theme_dark = "forest-dark"
theme_light = "forest-light"

# Set the theme with the theme_use method
style.theme_use(theme_dark)

# create a Frame widget with the custom style
frame = ttk.Frame(root, width=200, height=200)
frame.pack(expand=1, fill=BOTH)


def change_mode():
    global dark_mode 
    dark_mode = not dark_mode
    style.theme_use(theme_dark if dark_mode else theme_light)
    
    _style = ttk.Style()
    bg_color = _style.lookup('TFrame', 'background')
    fg_color = _style.lookup('TLabel', 'foreground')
    
    print(f"{bg_color}-{fg_color}")
    
    root.option_add('*TCombobox*Listbox*Background', bg_color)
    root.option_add('*TCombobox*Listbox*Foreground', fg_color)
    root.option_add('*TCombobox*Listbox*selectBackground', fg_color)
    root.option_add('*TCombobox*Listbox*selectForeground', bg_color)


button = ttk.Button(frame, text='mode', command=change_mode)
button.pack()

combo = ttk.Combobox(frame, width=40, justify=CENTER, values=[
'value1', 'value1', 'value1', 'value1', 'value1', 'value1', 'value1', 'value1'])
combo.pack()

root.mainloop()

Solution

  • Found a way to change themes through Tcl interpreter: here

    Finally code:

    from tkinter import *
    from tkinter import ttk
    
    
    root = Tk()
    root.geometry('550x500')
    
    # Create a style
    style = ttk.Style(root)
    
    # Import the tcl file
    root.tk.call("source", "forest-dark.tcl")
    # Import the tcl file
    root.tk.call("source", "forest-light.tcl")
    
    dark_mode = True
    theme_dark = "forest-dark"
    theme_light = "forest-light"
    
    # Set the theme with the theme_use method
    style.theme_use(theme_dark)
    
    # create a Frame widget with the custom style
    frame = ttk.Frame(root, width=200, height=200)
    frame.pack(expand=1, fill=BOTH)
    
    
    class MyCombo(ttk.Combobox):
        def __init__(self, master, **kwargs):
            ttk.Combobox.__init__(self, master, **kwargs)
            self._master = master
            self._change_color()
            self._master.bind("<<ThemeChanged>>", self._change_color)
        
        def _change_color(self, *args):
            _style = ttk.Style()
            # Get current Frame bg and Label fg
            bg_color = _style.lookup('TFrame', 'background')
            fg_color = _style.lookup('TLabel', 'foreground')
            
            # Create a new background for the combobox a little lighter if it's a dark theme, a little darker if it's light
            is_dark_mode = True if int(str(bg_color).replace("#", ''), 16) < int(str(fg_color).replace("#", ''), 16) else False
            new_bg_color = "#404040" if is_dark_mode else "#F8F8F8"
            
            # Set colors
            self._master.tk.eval('[ttk::combobox::PopdownWindow {}].f.l configure -background {} -foreground {}'.format(self, new_bg_color, fg_color))
    
    def change_mode():
        global dark_mode 
        dark_mode = not dark_mode
        style.theme_use(theme_dark if dark_mode else theme_light)
    
    button = ttk.Button(frame, text='Change mode', command=change_mode)
    button.pack()
    
    combo = MyCombo(frame, width=40, justify=CENTER, values=[
    'value1', 'value1', 'value1', 'value1', 'value1', 'value1', 'value1', 'value1'])
    combo.pack()
    
    root.mainloop()