Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tkinter OptionMenu: setting the width by the longest menu item in the list

Tags:

python

tkinter

Tkinter's OptionMenu sets its width based on the currently active menu item. I'm looking for a way to make it set its width based on the widest menu item, rather than the currently selected one to prevent the widget from resizing itself when the user selects a different menu item.

The suggested answer in the question Tkinter Option Menu Widget Changing Widths hides the problem, but doesn't solve it. (Hint: Not a duplicate!) The accepted answer is to set the widget to be sticky to both W and E. This hides the problem as long as the suggested size for the column is wider than all of the items in the box, but doesn't actually solve the problem.

I have (and prefer to have) a grid layout column with various widgets, which is dynamically sized based on the widths of the widgets. However, I'd prefer that the width reported by the OptionMenu widget is that of it widest item, not the currently selected ones, as the width of the whole column changes, if the user chooses an item which is wider than any of the other items.

All suggested methods I have found so far have either been to sticky the widget W and E which doesn't (in itself) solve the problem in my case, and setting an arbitrary constant width unrelated to the item lengths, which I don't want to do.

like image 912
nitro2k01 Avatar asked Feb 17 '14 07:02

nitro2k01


2 Answers

Thank you two who left answers, but neither using a monotype font or just setting the width to the length of the string are desirable solutions. The latter is too imprecise because the width parameter takes a number in a special "text unit" corresponding to the width of the the character "0" in the given font. So my final solution does the following:

  1. Figure out the font that the element is using.
  2. Use measure to find the width of each string. (Given here in pixels.)
  3. Divide the longest width by the width of the 0 character. You now have the width in "text units".

This gives the following code. Could probably be more Pythonic and be a class method of a subclass of OptionMenuetc, but it works.

def setMaxWidth(stringList, element):
    f = tkFont.nametofont(element.cget("font"))
    zerowidth=f.measure("0")
    w=max([f.measure(i) for i in stringList])/zerowidth

    element.config(width=w)

Out of curiosity, I wanted to know how the adjusted width would differ from the character length of each string in my data. Here's a simple loop doing that, and empirical data from my strings.

    for i in stringList:
        print len(i), float(f.measure(i))/float(zerowidth)

The data:

4 3.83333333333
24 20.1666666667
22 18.5
11 11.1666666667
12 11.3333333333
14 12.6666666667

Left column=string length. Right column=calculated width. In other words, a naive len(max(opts, key=len)) would give a bigger-than-needed widget in all cases. (This is of course to be expected.) I've also compared the computed widths of the widget using special cases of repeated W as well as repeated . and in all cases, these artificial tests string test agree well with the width requested by the widget "normally" with no width manually set.

like image 170
nitro2k01 Avatar answered Sep 21 '22 00:09

nitro2k01


If your GUI uses a fixed-width font, you could get the length of the longest element in the list that populates your OptionMenu, and set the width based on that.

from Tkinter import *

root = Tk()

opts = ['bacon', 'cereal', 'eggs']

oMenuWidth = len(max(opts, key=len))

v = StringVar(root)
v.set(opts[0])
oMenu = OptionMenu(root, v, *opts)
oMenu.config(width=oMenuWidth)
oMenu.grid()

mainloop()

I suppose even if it wasn't fixed length, you could perform a math operation on the width before implementing it, to get it close to right.

like image 41
atlasologist Avatar answered Sep 19 '22 00:09

atlasologist