Change width of dropdown listbox of a ttk combobox

Raoul Collenteur picture Raoul Collenteur · Oct 7, 2016 · Viewed 14k times · Source

I am trying to change to width of the popdown list of the ttk Combobox. Setting the width of the Combobox also changes the width of the Listbox, making part of the values unreadable.

I read this solution in Tk/Tcl but I am not familiar with this language and would like to solve the problem with Python. I tried changing the theme parameters but it does not seem to help. Below is a piece of sample code.

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.title("testing the combobox")
root.geometry('300x300+50+50')
fruit = ['apples are the best', 'bananas are better']

c = ttk.Combobox(root, values=fruit, width=10)
c.pack()

# Trying to change the width, does not work
c.option_add("*TCombobox*Listbox*Width", 50)

root.mainloop()

Anyone here that can help me out or give me some pointers?

Answer

dipanda picture dipanda · Nov 11, 2016

Elaboration of patthoyts' nice answer to get to an universal solution using a derived style instead of modifying the TCombobox style (but beware of a Tk bug, more on that later).

Basically, a new style with an unique name is created for every combobox (I don't know how this could scale up - maybe it's safer to apply it only where is needed). Also, combobox values are read from the widget itself and the longest one is taken: there's also a check to avoid making the popdown smaller than whe widget, if short text is inserted.

import tkinter as tk
import tkinter.ttk as ttk
import tkinter.font as tkfont

def on_combo_configure(event):
    combo = event.widget
    style = ttk.Style()
    # check if the combobox already has the "postoffest" property
    current_combo_style = combo.cget('style') or "TCombobox"
    if len(style.lookup(current_combo_style, 'postoffset'))>0:
        return
    combo_values = combo.cget('values')
    if len(combo_values) == 0:
        return
    longest_value = max(combo_values, key=len)
    font = tkfont.nametofont(str(combo.cget('font')))
    width = font.measure(longest_value + "0") - event.width
    if (width<0):
        # no need to make the popdown smaller
        return
    # create an unique style name using widget's id
    unique_name='Combobox{}'.format(combo.winfo_id())
    # the new style must inherit from curret widget style (unless it's our custom style!) 
    if unique_name in current_combo_style:
        style_name = current_combo_style 
    else:
        style_name = "{}.{}".format(unique_name, current_combo_style)

    style.configure(style_name, postoffset=(0,0,width,0))
    combo.configure(style=style_name)

root = tk.Tk()
root.title("testing the combobox")
root.geometry('300x300+50+50')
fruit = ['apples are the best', 'bananas are way more better']

c = ttk.Combobox(root, values=fruit, width=10)
c.bind('<Configure>', on_combo_configure)
c.pack()

c1 = ttk.Combobox(root, values=['shorter','than','widget'], width=15)
c1.bind('<Configure>', on_combo_configure)
c1.pack()

root.mainloop()

But...

As stated before, there's a bug in Tk Combobox: the postoffest property is read only from the TCombobox style, not from derived styles.

This can be fixed by editing [python-install-dir]\tcl\tk[version]\ttk\combobox.tcl; find this line in the PlacePopdown method:

set postoffset [ttk::style lookup TCombobox -postoffset {} {0 0 0 0}]

and replace it with:

set style [$cb cget -style]
set postoffset [ttk::style lookup $style -postoffset {} {0 0 0 0}]

Or, wait for my pull request to get merged and released.