Skip to content Skip to sidebar Skip to footer

How To Justify The Characters In Drop-down List Of A Combobox?

How to justify the values listed in drop-down part of a ttk.Combobox? I have tried justify='center' but that seems to only configure the selected item. Could use a resource link to

Solution 1:

I have a one-liner solution. Use the .option_add() method after declaring ttk.Combobox. Example:

cbb = ttk.Combobox(root, justify='center', values=(0, 1, 2)) # original
cbb.option_add('*TCombobox*Listbox.Justify', 'center')       # new line added

Solution 2:

Here's one pure Python way that gets close to what you want. The items in the dropdown list all get justified to fit within the Combobox's width (or a default value will be used).

Update

Part of the reason the initial version of my answer wasn't quite right was because the code assumed that a fixed-width font was being used. That's not the case on my test platform at least, so I've modified the code to actually measure the width of values in pixels instead of whole characters, and do essentially what it did originally, but in those units of string-length measure.

import tkinter as tk
import tkinter.font as tkFont
from tkinter import ttk

class CenteredCombobox(ttk.Combobox):
    DEFAULT_WIDTH = 20  # Have read that 20 is the default width of an Entry.

    def __init__(self, master=None, **kwargs):
        values = kwargs.get('values')
        if values:
            entry = ttk.Entry(None)  # Throwaway for getting the default font.
            font = tkFont.Font(font=entry['font'])
            space_width = font.measure(' ')

            entry_width = space_width * kwargs.get('width', self.DEFAULT_WIDTH)
            widths = [font.measure(str(value)) for value in values]
            longest = max(entry_width, *widths)

            justified_values = []
            for value, value_width in zip(values, widths):
                space_needed = (longest-value_width) / 2
                spaces_needed = int(space_needed / space_width)
                padding = ' ' * spaces_needed
                justified_values.append(padding + str(value))

            kwargs['values'] = tuple(justified_values)

        super().__init__(master, **kwargs)


root = tk.Tk()
ccb = CenteredCombobox(root, justify='center', width=10, values=('I', 'XLII', 'MMXVIII'))
ccb.pack()

root.mainloop()

Solution 3:

(Edit: Note that this solution works for Tcl/Tk versions 8.6.5 and above. @CommonSense notes that some tkinter installations may not be patched yet, and this solution will not work).

In Tcl ( I don't know python, so one of the python people can edit the question).

A combobox is an amalgamation of an 'entry' widget and a 'listbox' widget. Sometimes to make the configuration changes you want, you need to access the internal widgets directly.

Tcl:

% ttk::combobox .cb -values [list a abc def14 kjsdf]
.cb
% pack .cb
% set pd [ttk::combobox::PopdownWindow .cb]
.cb.popdown
% set lb $pd.f.l
.cb.popdown.f.l
% $lb configure -justify center

Python:

cb = ttk.Combobox(value=['a', 'abc', 'def14', 'kjsdf'])

cb.pack()
pd = cb.tk.call('ttk::combobox::PopdownWindow', cb)

lb = cb.tk.eval('return {}.f.l'.format(pd))

cb.tk.eval('{} configure -justify center'.format(lb))

Some caveats. The internals of ttk::combobox are subject to change. Not likely, not anytime soon, but in the future, the hard-coded .f.l could change.

ttk::combobox::PopdownWindow will force the creation of the listbox when it is called. A better method is to put the centering adjustment into a procedure and call that procedure when the combobox/listbox is mapped.

This will run for all comboboxes, you will need to check the argument in the proc to make sure that this is the combobox you want to adjust.

proc cblbhandler { w } {
   if { $w eq ".cb" } {
     set pd [ttk::combobox::PopdownWindow $w]
     set lb $pd.f.l
     $lb configure -justify center
   }
}

bind ComboboxListbox <Map> +[list ::cblbhandler %W]

Solution 4:

After digging through combobox.tcl source code I've come up with the following subclass of ttk.Combobox. JustifiedCombobox justifies the pop-down list's items almost precisely after pop-down list's been first created & customized and then displayed. After the pop-down list's been created, setting self.justify value to a valid one will again, customize the justification almost right after the pop-down list's first been displayed. Enjoy:

try:                        # In order to be able to import tkinter for
    import tkinter as tk    # either in python 2 or in python 3
    from tkinter import ttk
except:
    import Tkinter as tk
    import ttk


class JustifiedCombobox(ttk.Combobox):
    """
    Creates a ttk.Combobox widget with its drop-down list items
    justified with self.justify as late as possible.
    """

    def __init__(self, master, *args, **kwargs):
        ttk.Combobox.__init__(self, master, *args, **kwargs)
        self.justify = 'center'


    def _justify_popdown_list_text(self):
        self._initial_bindtags = self.bindtags()
        _bindtags = list(self._initial_bindtags)
        _index_of_class_tag = _bindtags.index(self.winfo_class())
        # This dummy tag needs to be unique per object, and also needs
        # to be not equal to str(object)
        self._dummy_tag = '_' + str(self)
        _bindtags.insert(_index_of_class_tag + 1, self._dummy_tag)
        self.bindtags(tuple(_bindtags))
        _events_that_produce_popdown = tuple([  '<KeyPress-Down>',
                                                '<ButtonPress-1>',
                                                '<Shift-ButtonPress-1>',
                                                '<Double-ButtonPress-1>',
                                                '<Triple-ButtonPress-1>',
                                                ])
        for _event_name in _events_that_produce_popdown:
            self.bind_class(self._dummy_tag, _event_name,
                                                self._initial_event_handle)


    def _initial_event_handle(self, event):
        _instate = str(self['state'])
        if _instate != 'disabled':
            if event.keysym == 'Down':
                self._justify()
            else:
                _ = self.tk.eval('{} identify element {} {}'.format(self,
                                                            event.x, event.y))
                __ = self.tk.eval('string match *textarea {}'.format(_))
                _is_click_in_entry = bool(int(__))
                if (_instate == 'readonly') or (not _is_click_in_entry):
                    self._justify()


    def _justify(self):
        self.tk.eval('{}.popdown.f.l configure -justify {}'.format(self,
                                                                self.justify))
        self.bindtags(self._initial_bindtags)


    def __setattr__(self, name, value):
        self.__dict__[name] = value
        if name == 'justify':
            self._justify_popdown_list_text()


def select_handle():
    global a
    _selected = a['values'][a.current()]
    if _selected in ("left", "center", "right"):
        a.justify = _selected


if __name__ == '__main__':
    root = tk.Tk()
    for s in ('normal', 'readonly', 'disabled'):
        JustifiedCombobox(root, state=s, values=[1, 2, 3]).grid()
    a = JustifiedCombobox(root, values=["Justify me!", "left", "center", "right"])
    a.current(0)
    a.grid()
    a.bind("<<ComboboxSelected>>", lambda event: select_handle())
    root.mainloop()

It basically makes use of bindtag event queue. This was mostly possible thanks to being able to creating a custom bindtag.


Post a Comment for "How To Justify The Characters In Drop-down List Of A Combobox?"