Question

Hello everyone and thanks in advance!

I've searched all around google and read almost every result i got and i still can't figure
it out, so please at least point me at some direction!
I read about pmw but i want to see if there is any way to do it with tkinter first.

I'm writing a simple enough program for DnD dice rolls and i have an OptionMenu
containing some of the dice someone needs to play. I also have an input field for entering
a die that is not included in my default options. My problem is that even though the new
option is added successfully, the options are not sorted.

I solved it at some point by destroying the OptionMenu when the new option was added,
sorting my List and then rebuilding the OptionMenu from scratch, but i was using the
place manager method at that time and i had to rewrite the program later because i had
some resolution problems. I'm using the pack manager now and destroying/rebuilding
is not an option unless i want to "re"pack all my widgets or make exclusive labels for them!

Here is a working sample of my code:

from tkinter import *

class DropdownExample(Frame):
    def __init__(self, master = None):
        Frame.__init__(self, master)
        self.pack(fill = 'both', expand = True)

        # Add Option Button
        self.addOptBtn = Button(self, text = "Add Option", command = self.add_option)

        # Option Input Field
        self.newOpt = IntVar()
        self.newOpt.set("Type a number")

        self.optIn = Entry(self)
        self.optIn['textvariable'] = self.newOpt

        # Dropdown Menu
        self.myOptions = [0, 1, 2]

        self.selOpt = IntVar()
        self.selOpt.set("Options")

        self.optMenu = OptionMenu(self, self.selOpt, *self.myOptions)

        # Positioning
        self.addOptBtn.pack(side = 'left', padx = 5)
        self.optIn.pack(side = 'left', padx = 5)
        self.optMenu.pack(side = 'left', padx = 5)


    def add_option(self):
        self.numToAdd = ""
        self.counter = 0

        try:
            self.numToAdd = int(self.optIn.get())                                           # Integer validation

            while self.counter < len(self.myOptions):                                       # Comparison loop & error handling
                if self.numToAdd == self.myOptions[self.counter]:
                    print("Already exists!")                    
                    break;

                elif self.numToAdd < 0:
                    print("No less than 0!")
                    break;

                elif self.counter < len(self.myOptions)-1:
                    self.counter += 1

                else:                                                                       # Dropdown menu option addition
                    self.myOptions.append(self.numToAdd)
                    self.myOptions.sort()

                    self.optMenu['menu'].add_command(label = self.numToAdd)

                    self.selOpt.set(self.numToAdd)

                    print("Added succesfully!")

                    self.counter += 2

        except ValueError:
            print("Type ONLY numbers!")


def runme():
    app = DropdownExample()
    app.master.title("Dropdown Menu Example")
    app.master.resizable(0, 0)
    app.mainloop()

runme()

I am using Python 3.3 on Windows 7

Was it helpful?

Solution

There is a set of insert_something() methods in Menu. You must keep your list sorted with each insert (bisect module).

from tkinter import *
import bisect

...
                else:                                                                       # Dropdown menu option addition
                    index = bisect.bisect(self.myOptions, self.numToAdd)
                    self.myOptions.insert(index, self.numToAdd)
                    self.optMenu['menu'].insert_command(index, label=self.numToAdd)
                    self.selOpt.set(self.numToAdd)
                    print("Added succesfully!", self.myOptions)
                    self.counter += 2

OTHER TIPS

Replace the line:

self.optMenu['menu'].add_command(label = self.numToAdd)

with:

for dit in self.myOptions:
    self.optMenu['menu'].delete(0)
for dat in self.myOptions:
    self.optMenu['menu'].add_command(label = dat)

The gotcha is that "add_command" takes the item to add to the menu, while "delete" takes the index of the item.

It seems that whatever way i choose to follow on this one, it all comes down to updating
the widget.
Updating a frame usually redraws a portion or the whole frame if needed.
So i went back to square one. I destroyed the widget, updated the list and then created it again.
As for the position, i used a label as a background only for the OptionMenu.
That way i can delete/replace my OptionMenu as i please, without moving any widgets around, as long as it stays inside it's label and it's the only one in there and of course as long as i don't move any labels.

This probably is a workaround, not a solution, but it get's the job done and with a bit
of optimization it can be used for any other widget having the same problem.

from tkinter import *

class DropdownExample(Frame):
    def __init__(self, master = None):
        Frame.__init__(self, master)
        self.pack(fill = 'both', expand = True)

        # Add Option Button
        self.addOptBtn = Button(self, text = "Add Option", command = self.add_option)

        # Option Input Field
        self.newOpt = IntVar()
        self.newOpt.set("Type a number")

        self.optIn = Entry(self)
        self.optIn['textvariable'] = self.newOpt

        # Dropdown Menu
        # menu's label
        self.optMenuLabel = Label(self)

        # option menu
        self.myOptions = [0, 1, 2]

        self.selOpt = IntVar()
        self.selOpt.set("Options")

        self.optMenu = OptionMenu(self.optMenuLabel, self.selOpt, *self.myOptions)

        # Positioning
        self.addOptBtn.pack(side = 'left', padx = 5)
        self.optIn.pack(side = 'left', padx = 5)
        self.optMenuLabel.pack(side = 'left', padx = 5)
        self.optMenu.pack(side = 'left', padx = 5)


    def add_option(self):
        self.numToAdd = ""
        self.counter = 0

        try:
            self.numToAdd = int(self.optIn.get())                                           # Integer validation

            while self.counter < len(self.myOptions):                                       # Comparison loop & error handling
                if self.numToAdd == self.myOptions[self.counter]:
                    print("Already exists!")                    
                    break;

                elif self.numToAdd < 0:
                    print("No less than 0!")
                    break;

                elif self.counter < len(self.myOptions)-1:
                    self.counter += 1

                else:                                                                       # Dropdown menu option addition
                    self.myOptions.append(self.numToAdd)
                    self.myOptions.sort()

                    self.selOpt.set(self.numToAdd)

                    self.optMenu.destroy()
                    self.optMenu = OptionMenu(self.optMenuLabel, self.selOpt, *self.myOptions)
                    self.optMenu.pack(side = 'left', padx = 5)

                    print("Added succesfully!", self.myOptions)
                    break;

        except ValueError:
            print("Type ONLY numbers!")


def runme():
    app = DropdownExample()
    app.master.title("Dropdown Menu Example")
    app.master.resizable(0, 0)
    app.mainloop()

runme()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top