Search code examples
python-3.xtkinterttk

Theming ttk widgets


I'm trying to do something simple -- change the fill color of a ttk Entry widget in a Python script to show an invalid entry. My question is not specifically how to do this (though I'd like to know!) but rather how to figure out how to do this given generally available documentation and resources.

I've read everything I can find about ttk and styles (including several StackOverflow questions), and while the process of applying styles looks straightforward if you know all the keywords/options you need, I can't figure out how to approach the problem if the keywords/options are unknown.

For an accepted answer to this question, I'd like to see the process of figuring out how to set the background color of a ttk Entry widget, that leads to a solution that works with Python. I want to learn to fish.


Solution

  • First of all, SO isnt designed to substitude regular documentation or tutorials. But I agree with the OP that ttk lack a little bit of a overall description.

    import tkinter as tk
    import tkinter.ttk as ttk
    from ttkthemes import ThemedStyle
    

    ttk isnt imported by tkinter itself and needs to be explicitly imported. It is recommended to import it as ttk to avoid name conflicts.

    style = ttk.Style(root)
    style.theme_use('default')
    

    The style class is designed to manipulate the styling database. Themes are always used, even if you didnt defined one. A list of themes can be found here or here. Or you can define your own with theme_create, like here

    button = ttk.Button(root, text='abc')
    button.pack(expand=True, fill='both')
    style_name = button.winfo_class()
    

    Every ttk.widget has the styling option and can be adressed by .winfo_class

    style.layout(style_name,
                 [('Button.border', {'sticky': 'nswe', 'border': '1', 'children': [
                     ('Button.focus', {'sticky': 'nswe', 'children': [
                         ('Button.padding', {'sticky': 'nswe', 'children': [
                             ('Button.label', {'sticky': 'nswe'})
                             ]})
                         ]})
                     ]}
                   )])
    

    Manipulating the layout gives you trivial options like to have a button on the right or left side. But in addition with element_create it can be a powerfull tool. Like Scrollbar.thumb, Scrollbar.trough, Downarrow.

    print(style.layout(style_name))
    print('Layout')
    

    Get the current layout and try this with different themes and may mix them together with element_create or try to use the vsapi under windows/visual_styles_api.

    print(style.element_options('Button.label'))
    print('label')
    print(style.map(style_name))
    print('map')
    

    To simply configure some colors I have found, and linked in my answer, a list of colors that can be configured but dosent mean they necessary have an effect.

    Options that are available don't necessarily have an effect, and it's not an error to modify a bogus option.

    For your entry background configure ttk::style configure TEntry -fieldbackground color that can also be found in the list of colors.

    widget = ttk.Entry(root, text='abc', style='My.TEntry')
    style.configure("My.TEntry",
                     foreground="grey",
                     fieldbackground="black")
    

    Example:

    import tkinter as tk
    import tkinter.ttk as ttk
    from ttkthemes import ThemedStyle
    
    bg_color = '#091424'
    fg_color = '#ffcfcf'
    arrow_bg = '#8c101e'
    border_color = '#db994c'
    highlight_color = '#372940'
    pressed_color = arrow_bg
    
    root = tk.Tk()
    style = ttk.Style(root)
    style.theme_use('clam')#background/arrowcolor
    
    frame =tk.Frame(root)
    frame.pack()
    
    widget = ttk.Spinbox(frame, text='abc', style="my.TSpinbox")
    widget.pack(expand=True, fill='both')
    
    style_name = widget.winfo_class()
    
    style.layout('my.TSpinbox',
                 [('Spinbox.field', {'sticky': 'nswe', 'children': [
                     ('Spinbox.background', {'sticky': 'nswe', 'children': [
                         ('Spinbox.padding', {'sticky': 'nswe', 'children': [
                             ('Spinbox.innerbg', {'sticky': 'nswe', 'children': [
                                 ('Spinbox.textarea', {'expand': '1', 'sticky': 'nswe'})
                                 ]})
                             ]}),
                         ('Spinbox.uparrow', {'side': 'top', 'sticky': 'nse'}),
                         ('Spinbox.downarrow', {'side': 'bottom', 'sticky': 'nse'})
                         ]})
                     ]})
                  ])
    
    
    
    style.configure('my.TSpinbox',
                    background=bg_color,
                    foreground=fg_color,
                    arrowcolor=arrow_bg,
                    arrowsize=20,
                    relief="raised",
                    bordercolor=border_color,
                    lightcolor='red',
                    darkcolor='green',
                    troughcolor='pink',
                    )
    
    style.map("my.TSpinbox",
              arrowcolor = [('pressed', pressed_color),
                            ('active', highlight_color),
                            ('disabled', '#ffffff')])
    
    
    print(style.layout(style_name))
    print('Layout')
    print(style.element_options('Spinbox.field'))
    print('field')
    print(style.element_options('Spinbox.downarrow'))
    print('downarrow')
    print(style.element_options('Spinbox.uparrow'))
    print('uparrow')
    print(style.element_options('Spinbox.padding'))
    print('padding')
    print(style.element_options('Spinbox.background'))
    print('background')
    print(style.element_options('Spinbox.textare'))
    print('textarea')
    print(style.element_options('Spinbox.innerbg'))
    print('innerbg')
    print(style.map(style_name))
    print('map')
    

    Additional References: