Search code examples
pythontkinterttktkinter.optionmenutkinter.style

How to make border around ttk.OptionMenu


While trying to make an entry frame I ran into a problem where I can't make border around ttk.OptionMenu in order to make it look similiar to ttk.Entry. (The two next to each other are in image)

Making OptionMenu

option = ttk.OptionMenu(bottom_container, self.have, 'ANY', 'ANY', '0', '1', style='vista.TMenubutton')
option.grid(column=1, row=2, sticky='we')

I tried using styles (want to still use vista/winnative look) and was able to make the optionmenu background white, but I couldn't find a way to fit in a border around it

OptionMenu next to Entry


Solution

  • this is the code i use for inspecting ttk widgets with a view to working out how to theme them:

    import tkinter as tk
    from tkinter import ttk
    
    root = tk.Tk()
    var = tk.StringVar()
    widget = ttk.OptionMenu(root, var, 'ANY', 'ANY', '0', '1')
    widget.grid(column=2, row=1, sticky='nesw')
    
    
    style = widget.winfo_class()
    
    s = ttk.Style()
    #s.theme_use('clam')
    elements = s.layout(style)
    
    def get_element_details(elem, _dict, depth=1):
        print('%selement: %s' % (''.join(['\t' for s in range(depth)]), elem))
        for key in _dict:
            if key != 'children':
                print('%s%s: %s' % (''.join(['\t' for s in range(depth+1)]), key, _dict[key]))
        print('%soption: %s' % (''.join(['\t' for s in range(depth+1)]), s.element_options(elem)))
        if 'children' in _dict:
            for child, child_dict in _dict['children']:
                get_element_details(child, child_dict, depth+1)
    
    print('element: %s' % style)
    print('option: %s' % str(s.element_options(style)))
    for elem, elem_dict in elements:
        get_element_details(elem, elem_dict)
    root.mainloop()
    

    and to put it simply without switching the theme to something like the clam theme there isn't any options in the widget to add a border (stupid I know)

    i don't have the vista theme to test with, but with the XP theme i get:

    element: TMenubutton
    option: ()
        element: Menubutton.dropdown
            side: right
            sticky: ns
            option: ()
        element: Menubutton.button
            expand: 1
            sticky: nswe
            option: ()
            element: Menubutton.padding
                expand: 1
                sticky: we
                option: ('-padding', '-relief', '-shiftrelief')
                element: Menubutton.label
                    sticky: 
                    option: ('-compound', '-space', '-text', '-font', '-foreground', '-underline', '-width', '-anchor', '-justify', '-wraplength', '-embossed', '-image', '-stipple', '-background')
    

    and with the clam theme:

    element: TMenubutton
    option: ()
        element: Menubutton.border
            sticky: nswe
            option: ('-bordercolor', '-lightcolor', '-darkcolor', '-relief', '-borderwidth')
            element: Menubutton.focus
                sticky: nswe
                option: ('-focuscolor', '-focusthickness')
                element: Menubutton.indicator
                    sticky: 
                    side: right
                    option: ('-arrowsize', '-arrowcolor', '-arrowpadding')
                element: Menubutton.padding
                    expand: 1
                    sticky: we
                    option: ('-padding', '-relief', '-shiftrelief')
                    element: Menubutton.label
                        sticky: 
                        side: left
                        option: ('-compound', '-space', '-text', '-font', '-foreground', '-underline', '-width', '-anchor', '-justify', '-wraplength', '-embossed', '-image', '-stipple', '-background')
    

    notice the addition of the border element?

    and if you try to copy the layout of the clam theme optionmenu to another theme using:

    newlayout = [('Menubutton.border', {'children': [('Menubutton.focus', {'children': [('Menubutton.indicator', {'sticky': '', 'side': 'right'}), ('Menubutton.padding', {'sticky': 'we', 'expand': '1', 'children': [('Menubutton.label', {'sticky': '', 'side': 'left'})]})], 'sticky': 'nswe'})], 'sticky': 'nswe'})]
    s.layout(style, newlayout)
    

    the result is:

    element: TMenubutton
    option: ()
        element: Menubutton.border
            sticky: nswe
            option: ('-relief',)
            element: Menubutton.focus
                sticky: nswe
                option: ()
                element: Menubutton.indicator
                    sticky: 
                    side: right
                    option: ('-direction', '-arrowsize', '-arrowcolor')
                element: Menubutton.padding
                    sticky: we
                    expand: 1
                    option: ('-padding', '-relief', '-shiftrelief')
                    element: Menubutton.label
                        sticky: 
                        side: left
                        option: ('-compound', '-space', '-text', '-font', '-foreground', '-underline', '-width', '-anchor', '-justify', '-wraplength', '-embossed', '-image', '-stipple', '-background')
    

    which no longer has options for actually setting the border. essentially the theme engine cannot properly handle layouts which have different elements to which it is expecting. so you will need to chose a theme which has widgets with all the elements you want to style.