python-3.xtkintertkinter-menu

Tkinter - Optionmenu - Is it possible to change the indicator arrow when the dropdown box is clicked by the user?


I have managed to change the indicator within the optionmenu box through using indicatoron=0, compound=RIGHT, image=dropImg, width=120 but I am now trying to change the arrow to point up when the box is clicked and the list of elements are shown. For instance: I tried activeindicatoron = 0 but this doesn't work. Does anyone have any ideas on whether this is possible in tkinter?

Thanks, Jacob


Solution

  • Yes it is possible. For instance, you can

    1. Use the postcommand option of the Optionmenu's menu to change the image on the button to an arrow pointing up.

    2. Use a binding to the <Unmap> event of the Optionmenu's menu to revert to the arrow pointing down when the menu disappears.

    Here is an example:

    import tkinter as tk
    root = tk.Tk()
    
    # up and down images
    down =b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x0e\x00\x00\x00\x07\x08\x06\x00\x00\x008G|\x19\x00\x00\x00\tpHYs\x00\x00\x10\x9b\x00\x00\x10\x9b\x01t\x89\x9cK\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\x9b\xee<\x1a\x00\x00\x00OIDAT\x18\x95\x95\xce\xb1\x0e@P\x0cF\xe1\xefzI\x8fc$\x12\x111\x19\xec\x9e\x12\xcb\x95 A\x9d\xa4K\xff\x9e\xb6\t\x13J\xffX \xa1\xc7\x16\xac\x19\xc5\xb1!*\x8fy\xf6BB\xf7"\r_\xff77a\xcd\xbd\x10\xedI\xaa\xa3\xd2\xf9r\xf5\x14\xee^N&\x14\xab\xef\xa9\'\x00\x00\x00\x00IEND\xaeB`\x82'
    up = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x0e\x00\x00\x00\x07\x08\x06\x00\x00\x008G|\x19\x00\x00\x00\tpHYs\x00\x00\x10\x9b\x00\x00\x10\x9b\x01t\x89\x9cK\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\x9b\xee<\x1a\x00\x00\x00\\IDAT\x18\x95\x8d\xd0A\n\x830\x14\x06\xe1/z,\x0f\xd2#\x19\\\xb4\x08Ep\xe1\xbe\xd7\xe8\xc5\xd2n\x12\x11%\x92\x81\xd9<\xfe\xd9\xbc^\x9d\x88\x01\xdf\x9b\xcd\x85\t?$\x8c\xadQ\xccQ1\xe5[\x95\x80\xe7)::\xd7\xa2\xd7MT|\xe7\xed\x1e-\rQqC\x17\xb0\xe2\xd1\xfa\x80\xcc\xe7\x0fO\xbe&\x9dv\xae\xef\x97\x00\x00\x00\x00IEND\xaeB`\x82'
    imgDown = tk.PhotoImage(master=root, data=down)
    imgUp = tk.PhotoImage(master=root, data=up)
    
    # create option menu
    var = tk.StringVar(root, 'a')
    option = tk.OptionMenu(root, var, *list('abcde'))
    option.configure(indicatoron=0, compound=tk.RIGHT, image=imgDown, width=120)
    
    # configure menu
    menu = option['menu']
    menu.configure(postcommand=lambda: option.configure(image=imgUp))
    menu.bind('<Unmap>', lambda ev: option.configure(image=imgDown))
    option.pack()
    
    root.mainloop()
    

    TTK version

    It is also possible to achieve this when using a ttk.OptionMenu and I think it look nicer because the arrow really replaces the indicator instead of being in the place of the image. This can be done by modifying the layout of the TMenubutton style:

    1. Create 'up' and 'down' elements from the images

      style.element_create('up', 'image', imgUp)
      style.element_create('down', 'image', imgDown)
      
    2. Create 'up.TMenubutton' and 'down.TMenubutton' layouts from the 'TMenubutton' layout (obtain with style.layout('TMenubutton')) by replacing 'Menubutton.indicator' by 'up' or 'down'.

    3. Use postcommand and <Unmap> to change the style of the Menubutton.

    Here is the code:

    import tkinter as tk
    from tkinter import ttk
    root = tk.Tk()
    
    # images
    down = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x0e\x00\x00\x00\x07\x08\x06\x00\x00\x008G|\x19\x00\x00\x00\tpHYs\x00\x00\x10\x9b\x00\x00\x10\x9b\x01t\x89\x9cK\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\x9b\xee<\x1a\x00\x00\x00OIDAT\x18\x95\x95\xce\xb1\x0e@P\x0cF\xe1\xefzI\x8fc$\x12\x111\x19\xec\x9e\x12\xcb\x95 A\x9d\xa4K\xff\x9e\xb6\t\x13J\xffX \xa1\xc7\x16\xac\x19\xc5\xb1!*\x8fy\xf6BB\xf7"\r_\xff77a\xcd\xbd\x10\xedI\xaa\xa3\xd2\xf9r\xf5\x14\xee^N&\x14\xab\xef\xa9\'\x00\x00\x00\x00IEND\xaeB`\x82'
    up = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x0e\x00\x00\x00\x07\x08\x06\x00\x00\x008G|\x19\x00\x00\x00\tpHYs\x00\x00\x10\x9b\x00\x00\x10\x9b\x01t\x89\x9cK\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\x9b\xee<\x1a\x00\x00\x00\\IDAT\x18\x95\x8d\xd0A\n\x830\x14\x06\xe1/z,\x0f\xd2#\x19\\\xb4\x08Ep\xe1\xbe\xd7\xe8\xc5\xd2n\x12\x11%\x92\x81\xd9<\xfe\xd9\xbc^\x9d\x88\x01\xdf\x9b\xcd\x85\t?$\x8c\xadQ\xccQ1\xe5[\x95\x80\xe7)::\xd7\xa2\xd7MT|\xe7\xed\x1e-\rQqC\x17\xb0\xe2\xd1\xfa\x80\xcc\xe7\x0fO\xbe&\x9dv\xae\xef\x97\x00\x00\x00\x00IEND\xaeB`\x82'
    imgDown = tk.PhotoImage(master=root, data=down)
    imgUp = tk.PhotoImage(master=root, data=up)
    
    # style
    style = ttk.Style(root)
    style.theme_use('clam')
    style.element_create('up', 'image', imgUp)
    style.element_create('down', 'image', imgDown)
    
    style.layout('up.TMenubutton', [('Menubutton.border',
      {'sticky': 'nswe',
       'children': [('Menubutton.focus',
         {'sticky': 'nswe',
          'children': [('Menubutton.up', {'side': 'right', 'sticky': ''}),  # replace the indicator by up arrow
           ('Menubutton.padding',
            {'expand': '1',
             'sticky': 'we',
             'children': [('Menubutton.label',
               {'side': 'left', 'sticky': ''})]})]})]})])
    
    style.layout('down.TMenubutton', [('Menubutton.border',
      {'sticky': 'nswe',
       'children': [('Menubutton.focus',
         {'sticky': 'nswe',
          'children': [('Menubutton.down', {'side': 'right', 'sticky': ''}),  # replace the indicator by down arrow
           ('Menubutton.padding',
            {'expand': '1',
             'sticky': 'we',
             'children': [('Menubutton.label',
               {'side': 'left', 'sticky': ''})]})]})]})])
    
    var = tk.StringVar(root, 'a')
    option = ttk.OptionMenu(root, var, *list('abcde'))
    option.configure(style='down.TMenubutton')
    menu = option['menu']
    menu.configure(postcommand=lambda: option.configure(style='up.TMenubutton'))
    menu.bind('<Unmap>', lambda ev: option.configure(style='down.TMenubutton'))
    option.pack()
    
    root.mainloop()