Python Forum
Adding a sub menu to a ttk.OptionMrnue ?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Adding a sub menu to a ttk.OptionMrnue ?
#1
Hi all,

Can somebody tell me how to add a sub menu to python's ttk.OptionMenu I've created a drop down menu.
but I am unable to fined how to add a submenu to one of the options in the main menu.

Or That's if it is implemented yet.

any advice would be appreciated.

Robo
Reply
#2
That is not a feature that option menu supports. You cannot add a submenu to one of the options, because the options are radio buttons. To have submenus you need the option to be menus.

OptionMenu is really a convenience class for making a menu button with a menu of radio buttons. Here's an example of making an OptionMenu-like thing that supports cascading.

https://stackoverflow.com/questions/2435...-be-nested

It wouldn't be difficult to use this code as a starting point for making your own CascadedOptionMenu class.

I don't see how such a thing could be used to select 2 options as it only has 1 value. The cascading being more a mechanism for organizing the values than adding structure to the result.

You could organize your options to get two values using a regular option menu like this:
import tkinter as tk

options = {
    "1 A": (1, "A"),
    "1 B": (1, "B"),
    "1 C": (1, "C"),
    "2 A": (2, "A"),
    "2 B": (2, "B"),
    "2 C": (2, "C"),
    "3": 3,
    "4": 4,
}

root = tk.Tk()
var = tk.StringVar(root, list(options)[0])
menu = tk.OptionMenu(root, var, *list(options))
menu.pack(padx=50, pady=50)
var.trace_add("write", lambda x, y, z: print(var.get(), options[var.get()]))
root.mainloop()
Reply
#3
Hi,

That might work using the original tk,OptionMenu.

I'm using the newer ttk.OptioMenu I've tried suing the earlier method but couldn't get it to work.

any other thoughts.

Robo
Reply
#4
ttk and tk buttons work the same except styling. ttk is designed to be drop in compatible with ttk. The example making a OptionMenu out of a tkinter menu button and menu of radio buttons can be made using a ttk menu button.
import tkinter as tk
import tkinter.ttk as ttk


class Example(tk.Frame):
    def __init__(self, parent):
        ttk.Frame.__init__(self, parent)

        items = {"one": ["a","b","c"],
                 "two": ["d","e","f"],
                 "three": ["g","h","i"]}

        self.var = tk.StringVar(self, "a")

        self.menubutton = ttk.Menubutton(self, textvariable=self.var)
        self.topMenu = tk.Menu(self.menubutton, tearoff=False)
        self.menubutton.configure(menu=self.topMenu)

        for key in sorted(items.keys()):
            menu = tk.Menu(self.topMenu)
            self.topMenu.add_cascade(label=key, menu=menu)
            for value in items[key]:
                menu.add_radiobutton(label=value, variable=self.var, value=value)

        self.menubutton.pack()

if __name__ == "__main__":
    root = tk.Tk()
    Example(root).pack(fill="both", expand=True)
    root.mainloop()
And what does this mean?
Quote:That might work using the original tk,OptionMenu.

I was thinking about how an OptionMenu is really just a bunch of radio buttons. The rules for radio buttons apply to t the option menu. All "options" need to be unique. Even if there was a way to make a cascaded OptionMenu, you could not repeat any of the options. This would be invalid because there is no way to differentiate A 1 from B 1.
Output:
A 1 2 3 B 1 2 3
Reply
#5
Hi,
Thanks for the response but it's not quite what I wanted.

This is how I create my main drop down menu which works fine, I hope it helps.
I wanted to add a submenu to Item6, which are: sub1 and sub2


Var1 = tk.StringVar(Fbtn1)
list=[' Item1 ) ',' Item2 ',' Item3 ',' Item4 ',' Item5 ',' Item6 ) ']

Mbtn1 = ttk.OptionMenu(Fbtn1, Var1, list[2], *list,style= 'O.TLabel',
command = (Dropdown_Main_Menu))
Mbtn1['menu'].configure(background= 'lightgray') # Change Menu Background Colour

Any advise how to add a submenu to the code would be most appreciated.

Robo
Reply
#6
Maybe this is what you want. It is the same thing as before, but it uses OptionMenu to create the menu button and menu. Adding the submenu is done the same way as the previous example. Create a new menu. Add radio buttons for the options, Add the new menu to the existing menu using add_cascade.
import tkinter as tk
import tkinter.ttk as ttk


root = tk.Tk()
var = tk.StringVar(root, "One")
option_menu = ttk.OptionMenu(root, var, "One", "Two", "Three")
option_menu.pack()

# Create a new menu with radio buttons and add to OptionMenu["menu"]
imenu = option_menu["menu"]
submenu = tk.Menu(imenu, tearoff=False)
submenu.add_radiobutton(label="A", variable=var)
submenu.add_radiobutton(label="B", variable=var)
imenu.add_cascade(label="Four", menu=submenu)

var.trace_add("write", lambda a, b, c: print(var.get()))
root.mainloop()
And here it is written as a subclass.
class TackyOptionMenu(ttk.OptionMenu):
    def tack_on(self, label, *options):
        menu = self["menu"]
        var = self._variable  # This is the variable passed in when the OptionMenu was created.
        submenu = tk.Menu(menu, tearoff=False)  # Create the submenu
        for option in options:
            submenu.add_radiobutton(label=option, variable=var)  # Add the radio buttons
        menu.add_cascade(label=label, menu=submenu)  # Add submenu to menu.


root = tk.Tk()
var = tk.StringVar(root, "One")
option_menu = TackyOptionMenu(root, var, "One", "Two", "Three")
option_menu.tack_on("Four", "A", "B", "C")
option_menu.pack()

var.trace_add("write", lambda a, b, c: print(var.get()))
root.mainloop()
I don't like this approach as much as starting from scratch. You are limited to tacking on the submenu at the very end. I'd rather have total control of how the menus are populated.
import tkinter as tk
import tkinter.ttk as ttk


class CascadedOptionMenu(ttk.Menubutton):
    """An option menu with cascaded submenus."""
    def __init__(self, parent, options, variable=None, command=None, **kwargs):
        self.command = command
        super().__init__(parent, **kwargs)
        self.var = variable if variable else tk.StringVar(self, options[0])
        self.var.trace_add("write", self._select_cb)
        self.configure(textvariable=self.var)

        menu = tk.Menu(self, tearoff=False)
        self["menu"] = menu
        for option in options:
            if isinstance(option, str):
                menu.add_radiobutton(label=option, variable=self.var)
            else:
                label, *suboptions = option
                submenu = tk.Menu(self, tearoff=False)
                for option in suboptions:
                    submenu.add_radiobutton(label=option, variable=self.var)
                menu.add_cascade(label=label, menu=submenu)

    @property
    def value(self):
        """Return value."""
        return self.var.get()

    @value.setter
    def value(self, new_value):
        """Set value."""
        self.var.set(new_value)

    def _select_cb(self, *_):
        """Callback when selelction changes.  Calls command."""
        if self.command:
            self.command(self.value)


if __name__ == "__main__":
    root = tk.Tk()
    menu = CascadedOptionMenu(
        root,
        options=["One", ("Two", "A", "B"), "Three", ("Four", "C", "D")],
        command=print)
    menu.pack()
    root.mainloop()
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020