1

I want to extract the selected data from a drop down menu in Tkinter. The drop down menu changes based on a previous drop down menu selection.

Basically, how do I save a variable from a lambda function?

I've tried declaring a global variable, and dictionary value but the problem still is that I am overwriting the original drop down menu in my function and the code is still reading the value of the original drop down. Ideally, the code would just update the actual drop down list and not overwrite the original one. Apologies for my clunky, un-pythonic code.

import tkinter as tk

HEIGHT = 500
WIDTH = 500

root = tk.Tk()

canvas = tk.Canvas(root, height = HEIGHT, width = WIDTH)
canvas.pack()

subframe = tk.Frame(root, bg = 'gray', bd = 10)
subframe.place(relx = 0.05, rely = 0.05, relwidth = 0.9, relheight = 0.9)

subframe.rowconfigure(tuple(range(4)), weight = 1)
subframe.columnconfigure(tuple(range(4)), weight = 1)

inputs = {}  ### dictionary for saved label *** I know there HAS to be a better way 

### --- dropdown1 Input
label10 = tk.Label(subframe, text = 'Label 1', font = ('Century', 12), bg = 'gray', fg = 'white')
label10.grid(row = 1, column = 0, sticky = 'ew', padx = 5, pady = 5)

dropdown1_ID_series = ['A', 'B', 'C']

#global dropdown2_ID_var

def update_test_data_dropdown(dropdown1ID):
    if dropdown1ID == 'C':
        dropdown2_ID_series = ['1', '2', '3']
        dropdown2_ID_var = tk.StringVar(root)
        dropdown2_ID_var.set(dropdown2_ID_series[0])
        dropdown2_ID_entry = tk.OptionMenu(subframe, dropdown2_ID_var, *dropdown2_ID_series)
        dropdown2_ID_entry.config(width=10)
        dropdown2_ID_entry.grid(row = 2, column = 1, sticky = 'ew', padx = 5, pady = 5)
        inputs['dropdown2'] = dropdown2_ID_var.get()
        print (inputs['dropdown2'])
    else:
        dropdown2_ID_series = ['4', '5','6']
        dropdown2_ID_var = tk.StringVar(root)
        dropdown2_ID_var.set(dropdown2_ID_series[0])
        dropdown2_ID_entry = tk.OptionMenu(subframe, dropdown2_ID_var, *dropdown2_ID_series)
        dropdown2_ID_entry.config(width=10)
        dropdown2_ID_entry.grid(row = 2, column = 1, sticky = 'ew', padx = 5, pady = 5)
        inputs['dropdown2'] = dropdown2_ID_var.get()
    return dropdown2_ID_var  ## need to figure out how to export dropdown2_ID_var

dropdown1_ID_var = tk.StringVar(root)
dropdown1_ID_var.set(dropdown1_ID_series[0])
dropdown1_ID_entry = tk.OptionMenu(subframe, dropdown1_ID_var, *dropdown1_ID_series, command = lambda x: update_test_data_dropdown(x))
dropdown1_ID_entry.config(width=10)
dropdown1_ID_entry.grid(row = 1, column = 1, sticky = 'ew', padx = 5, pady = 5)

### --- dropdown 2 input
label20 = tk.Label(subframe, text = 'Label 2', font = ('Century', 12), bg = 'gray', fg = 'white')
label20.grid(row = 2, column = 0, sticky = 'ew', padx = 5, pady = 5)

dropdown2_ID_series = ['D', 'E', 'F']

dropdown2_ID_var = tk.StringVar(root)
dropdown2_ID_var.set(dropdown2_ID_series[0])
dropdown2_ID_entry = tk.OptionMenu(subframe, dropdown2_ID_var, *dropdown2_ID_series)
dropdown2_ID_entry.config(width=10)
dropdown2_ID_entry.grid(row = 2, column = 1, sticky = 'ew', padx = 5, pady = 5)

inputs['dropdown2'] = dropdown2_ID_var.get()
print (inputs['dropdown2'])

def submit():
    label11 = tk.Label(subframe, text = dropdown1_ID_var.get(), font = ('Century', 12), bg = 'gray', fg = 'white')
    # label22 = tk.Label(subframe, text = dropdown2_ID_var.get(), font = ('Century', 12), bg = 'gray', fg = 'white')
    label22 = tk.Label(subframe, text = inputs['dropdown2'], font = ('Century', 12), bg = 'gray', fg = 'white')
    label11.grid(row = 1, column = 3, padx = 5, pady = 5)
    label22.grid(row = 2, column = 3, padx = 5, pady = 5)

### --- RUN BUTTON
button = tk.Button(subframe, text = 'SUBMIT', font = ('Century', 12, 'bold'), command = lambda: submit())
button.grid(row = 3, column = 3, ipadx = 20, padx = 5, pady = 15)


root.mainloop() 

When hitting SUBMIT on the above code, I want the selected value in the dropdowns to print next to the drop down.

2 Answers 2

2

I think the following does what you want. It uses the textvariable= option Labels accept so they will automatically update themselves when the associated StringVar's value is changed. It also shows how the change the options associated with an existing OptionMenu widget (addressing the "how do I save a variable from a lambda command" part of your question).

Note I also reformatted your code per the PEP 8 - Style Guide for Python Code guidelines to make it more readable.

import tkinter as tk

HEIGHT = 500
WIDTH = 500

root = tk.Tk()

canvas = tk.Canvas(root, height=HEIGHT, width=WIDTH)
canvas.pack()

subframe = tk.Frame(root, bg='gray', bd=10)
subframe.place(relx=0.05, rely=0.05, relwidth=0.9, relheight=0.9)

subframe.rowconfigure(tuple(range(4)), weight=1)
subframe.columnconfigure(tuple(range(4)), weight=1)

### --- dropdown1 Input
label10 = tk.Label(subframe, text='Label 1', font=('Century', 12), bg='gray', fg='white')
label10.grid(row=1, column=0, sticky='ew', padx=5, pady=5)

dropdown1_ID_series = ['A', 'B', 'C']

def set_menu(entry, values, default=None):
    """Utility to change OptionMenu widget choices and associated StringVar."""
    varname = entry.cget('textvariable')  # Get name associated with StringVar.
    variable = tk.StringVar(name=varname)  # Get reference to it.
    menu = entry['menu']
    menu.delete(0, 'end')
    for value in values:
        menu.add_command(label=value, command=lambda val=value: variable.set(val))
    variable.set('' if default is None else default)


def update_test_data_dropdown(dropdown1ID):
    label11.grid_remove()  # Hide.
    label22.grid_remove()  # Hide.

    dropdown2_ID_series[:] = (['1', '2', '3'] if dropdown1ID == 'C'
                                else ['4', '5', '6'])
    # Change second OptionMenu.
    set_menu(dropdown2_ID_entry, dropdown2_ID_series, default=dropdown2_ID_series[0])


dropdown1_ID_var = tk.StringVar(root)
dropdown1_ID_var.set(dropdown1_ID_series[0])
dropdown1_ID_entry = tk.OptionMenu(subframe, dropdown1_ID_var, *dropdown1_ID_series,
                                   command=update_test_data_dropdown)
dropdown1_ID_entry.config(width=10)
dropdown1_ID_entry.grid(row=1, column=1, sticky='ew', padx=5, pady=5)

### --- dropdown 2 input
label20 = tk.Label(subframe, text='Label 2', font=('Century', 12),
                   bg='gray', fg='white')
label20.grid(row=2, column=0, sticky='ew', padx=5, pady=5)

dropdown2_ID_series = ['D', 'E', 'F']
dropdown2_ID_var = tk.StringVar(root)
dropdown2_ID_var.set(dropdown2_ID_series[0])
dropdown2_ID_entry = tk.OptionMenu(subframe, dropdown2_ID_var, *dropdown2_ID_series,
                                   command=update_test_data_dropdown)
dropdown2_ID_entry.config(width=10)
dropdown2_ID_entry.grid(row=2, column=1, sticky='ew', padx=5, pady=5)

label11 = tk.Label(subframe, textvariable=dropdown1_ID_var, font=('Century', 12),
                   bg='gray', fg='white')
label11.grid(row=1, column=3, padx=5, pady=5)
label11.grid_remove()  # Initially hidden (but remembers grid location).

label22 = tk.Label(subframe, textvariable=dropdown2_ID_var, font=('Century', 12),
                   bg='gray', fg='white')
label22.grid(row=2, column=3, padx=5, pady=5)
label22.grid_remove()  # Initially hidden (but remembers grid location).

def submit():
    label11.grid()  # Unhide.
    label22.grid()  # Unhide.

### --- Run Button
button = tk.Button(subframe, text='SUBMIT', font=('Century', 12, 'bold'), command=submit)
button.grid(row=3, column=3, ipadx=20, padx=5, pady=15)


root.mainloop()

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you for your answer. It's very close. Just after the menu is changed, no matter which value is clicked, the last value in the list is recorded. i.e. changing drop down label 1 to 'C', clicking on '1', '2', or '3' will give '3' every time. I used tk._setit() but couldn't figure out how to fix it in your code.
@HeatherB.: Fixed it — made somewhat of rookie mistake when writing the lambda being passed to menu.add_command() in place of tk._setit() — but fortunately was something relatively easy to correct.
looks great! Thanks!
0

I figured out a better way to update the drop down list by adding the optionmenu entry and variable to the function, deleting the original list, and editing through 'menu' and add_command. The two tricky/annoying things is having to create a loop to add the new list values individually then most importantly using tk._setit() to have the values actually selectable. I would be happy to find out if there is a better way but this at least works.

import tkinter as tk

HEIGHT = 500
WIDTH = 500

root = tk.Tk()

canvas = tk.Canvas(root, height = HEIGHT, width = WIDTH)
canvas.pack()

subframe = tk.Frame(root, bg = 'gray', bd = 10)
subframe.place(relx = 0.05, rely = 0.05, relwidth = 0.9, relheight = 0.9)

subframe.rowconfigure(tuple(range(4)), weight = 1)
subframe.columnconfigure(tuple(range(4)), weight = 1)

### --- dropdown1 Input
label10 = tk.Label(subframe, text = 'Label 1', font = ('Century', 12), bg = 'gray', fg = 'white')
label10.grid(row = 1, column = 0, sticky = 'ew', padx = 5, pady = 5)

dropdown1_ID_series = ['A', 'B', 'C']

def update_test_data_dropdown(dropdown1ID, dropdown2_ID_entry, dropdown2_ID_var):
    if dropdown1ID == 'C':
        dropdown2_ID_series2 = ['1', '2', '3']
        dropdown2_ID_entry['menu'].delete(0,'end')
        for v in dropdown2_ID_series2:
            dropdown2_ID_entry['menu'].add_command(label = v, command= tk._setit(dropdown2_ID_var, v))
    else:
        dropdown2_ID_series2 = ['4', '5','6']
        dropdown2_ID_entry['menu'].delete(0,'end')
        for v in dropdown2_ID_series2:
            dropdown2_ID_entry['menu'].add_command(label = v, command= tk._setit(dropdown2_ID_var, v))
    dropdown2_ID_var.set(dropdown2_ID_series2[0])
    return dropdown2_ID_var  ## need to figure out how to export dropdown2_ID_var

dropdown1_ID_var = tk.StringVar(root)
dropdown1_ID_var.set(dropdown1_ID_series[0])
dropdown1_ID_entry = tk.OptionMenu(subframe, dropdown1_ID_var, *dropdown1_ID_series, command = lambda x: update_test_data_dropdown(x, dropdown2_ID_entry, dropdown2_ID_var))
dropdown1_ID_entry.config(width=10)
dropdown1_ID_entry.grid(row = 1, column = 1, sticky = 'ew', padx = 5, pady = 5)

### --- dropdown 2 input
label20 = tk.Label(subframe, text = 'Label 2', font = ('Century', 12), bg = 'gray', fg = 'white')
label20.grid(row = 2, column = 0, sticky = 'ew', padx = 5, pady = 5)

dropdown2_ID_series = ['D', 'E', 'F']

dropdown2_ID_var = tk.StringVar(root)
dropdown2_ID_var.set(dropdown2_ID_series[0])
dropdown2_ID_entry = tk.OptionMenu(subframe, dropdown2_ID_var, *dropdown2_ID_series)
dropdown2_ID_entry.config(width=10)
dropdown2_ID_entry.grid(row = 2, column = 1, sticky = 'ew', padx = 5, pady = 5)


def submit():
    label11 = tk.Label(subframe, text = dropdown1_ID_var.get(), font = ('Century', 12), bg = 'gray', fg = 'white')
    label22 = tk.Label(subframe, text = dropdown2_ID_var.get(), font = ('Century', 12), bg = 'gray', fg = 'white')
    label11.grid(row = 1, column = 3, padx = 5, pady = 5)
    label22.grid(row = 2, column = 3, padx = 5, pady = 5)

### --- RUN BUTTON
button = tk.Button(subframe, text = 'SUBMIT', font = ('Centur', 12, 'bold'), command = lambda: submit())
button.grid(row = 3, column = 3, ipadx = 20, padx = 5, pady = 15)


root.mainloop()

1 Comment

You don't have to use the private tk._setit() class to do what's needed here — see the answer I posted.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.