Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change width of dropdown listbox of a ttk combobox

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?

like image 933
Raoul Collenteur Avatar asked Oct 07 '16 10:10

Raoul Collenteur


People also ask

How do you change the size of a Combobox in Python?

Simple, add font attribute. For eg. ttk. Combobox(root, values=[1, 2, 3, 4, 5], state="readonly", width=1, font="Verdana 12 bold") .

How do I use TTK Combobox?

Use ttk. Combobox(root, textvariable) to create a combobox. Set the state property to readonly to prevent users from entering custom values. A combobox widget emits the '<<ComboboxSelected>>' event when the selected value changes.

How to set default value for Combobox in tkinter python?

It can be set by listing all the records in a variable that needs to be present in the Combobox. By specifying the index of the particular value in the current(index) method, we can set the default value in the Combobox widget.

How do I clear my TTK Combobox?

You can create a combobox widget by initializing the constructor of Combobox(root, width, text) widget. Consider the case, if the user wants to clear the selected value from the combobox widget, the only way you can do so is to set the value of the combobox widget as NULL by using the set (' ') method.


3 Answers

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.

like image 124
dipanda Avatar answered Oct 09 '22 19:10

dipanda


The Tk library file that implements the menu posting (ttk/combobox.tcl) explicitly reads the width of the combobox widget and sets the menu toplevel to be the same width (in ttk::combobox::PlacePopdown). However you can apply a themed style to override this using the -postoffset configuration option. This exists to allow for some themes to offset the dropdown but we can set this to allow for a custom width. Note: all the widgets using this style would get the same width so you might want to derive a custom style.

In Tcl/Tk this is: ttk::style configure TCombobox -postoffset {0 0 300 0} to setup the dropdown to 300 pixels wide (x y width height).

UPDATE

The following python tkinter code adds some code to the <Configure> event to get the size of the combobox and update the postoffset width such that the dropdown will match the size of the first string in the list of strings. The result looks like this: screenshot of an amended width combobox dropdown

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

def on_combo_configure(event):
    global fruit
    font = tkfont.nametofont(str(event.widget.cget('font')))
    width = font.measure(fruit[0] + "0") - event.width
    style = ttk.Style()
    style.configure('TCombobox', postoffset=(0,0,width,0))

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.bind('<Configure>', on_combo_configure)
c.pack()

root.mainloop()

We do this using the event binding as this ensures we can get the actual size of the widget on screen to remove that width from the offset.

like image 24
patthoyts Avatar answered Oct 09 '22 18:10

patthoyts


Late to the party, but I encountered this issue recently...

I tried dipanda's solution, and confirmed that the derived styles were being altered during the <Configure> event, but for some reason the postoffset option wasn't having any effect. It was being set, but the pop-down remained the same width as the original widget.

What worked for my purpose was the following; pretty similar to dipanda's code:

def combo_configure(event):
    combo = event.widget
    style = ttk.Style()

    long = max(combo.cget('values'), key=len)

    font = tkfont.nametofont(str(combo.cget('font')))
    width = max(0,font.measure(long.strip() + '0') - combo.winfo_width())

    style.configure('TCombobox', postoffset=(0,0,width,0))

But here's where I went weird:

Instead of binding this to the <Configure> event, I instead bound it to the <ButtonPress> event:

widget.bind('<ButtonPress>', combo_configure)

I acknowledge that the changes affect all Comboboxes in the window, but since you can only have one dropped down at a time, it doesn't really matter; it's transparent as far as the user is concerned. Plus, no need to modify the combobox.tcl file!

Just my $1.05...

like image 2
MickeyPvX Avatar answered Oct 09 '22 19:10

MickeyPvX