Search code examples
pythonooptkintertkinter.optionmenu

Changing one OptionMenu changes the second one


In my code below I have two option menus which are populated with the same list. In the final application the list is generated by importing a .csv file.

The user should be able to select two entries from the list.

Now the problem is, that changing the first option menu, will change instead the second one. The second one, however, works as expected.

I guess the function update_file_list_selection() and lambda function is implemented badly.

import tkinter as tk
from tkinter import ttk


class File_Selection():
    def __init__(self, frame, text):
        self.frame = frame
        self.text = text

        self.label_file = tk.Label(self.frame, text=text)
        self.label_file.pack()

        self.variable_file = tk.StringVar(self.frame)
        self.option_list = ["no file loaded"]
        self.variable_file.set(self.option_list[0])
        self.optionmenu_file = tk.OptionMenu(self.frame, self.variable_file,
                                             *self.option_list)
        self.optionmenu_file.pack()


class View:
    def __init__(self, view, update_list):

        self.view = view
        self.view.title("Test")
        self.view.geometry("320x240")
        self.view.resizable(False, False)

        self.frame = tk.Frame(self.view)
        self.frame.pack()

        self.button = tk.Button(self.frame, text="Update", command=update_list)
        self.button.pack()

        self.file_one = File_Selection(self.frame, "File 1")
        self.file_two = File_Selection(self.frame, "File 2")


class Controller:
    def __init__(self):
        self.root = tk.Tk()
        self.view = View(self.root, lambda: self.update_file_list_selection())

        self.files = ["File 1", "File 2", "File 3", "File 4"]

    def run(self):
        self.root.mainloop()

    def update_file_list_selection(self):

        self.active_file_selection = [self.view.file_one, self.view.file_two]

        for file_selection in self.active_file_selection:

            self.menu = file_selection.optionmenu_file["menu"]
            self.menu.delete(0, "end")

            for x in self.files:
                file_selection.option_list.append(x)
                self.menu.add_command(label=x,
                        command=lambda value=x: file_selection.variable_file.set(value))

            file_selection.variable_file.set(self.files[0])


if __name__ == "__main__":
    c = Controller()
    c.run()

Solution

  • I guess the function update_file_list_selection() and lambda function is implemented badly.

    That is a correct guess.

    The reason is a common problem with using lambda - when you do command=lambda value=x: file_selection.variable_file.set(value), the value of file_selection won't be the value from the loop, it will end up being the value of the final time that variable was set. You can solve this by binding the value to the lambda as a default argument:

    self.menu.add_command(label=x, command=lambda value=x, fs=file_selection: fs.variable_file.set(value))
    

    The above will make sure that inside the lambda body, fs will be set to the value of file_selection at the time the menu item is made rather than the value at the time the item is selected.

    You'll still end up with OptionMenu items that don't behave exactly the same as normal OptionMenu items, but in this specific example that doesn't seem to matter since you don't have a command associated with the OptionMenu as a whole.