1

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()
martineau
  • 119,623
  • 25
  • 170
  • 301
Bakira
  • 79
  • 9
  • Sorry, it's unclear from your description and your code what you're trying to accomplish as well as exactly how to run the code to reproduce the program. – martineau Jan 09 '22 at 17:11
  • The implemented button is only to load the list into the OptionMenu. After this, each OptionMenu should hold the same data. In the final application the entries are paths to files which hold data. The user can now choose from the list which entry he is interested in. For example: he want to compare "File 1" and "File 4", therefore he select "File 1" in OptionMenu 1 and "File 4" in OptionMenu 2. But, with my code above, if I select "File 1" in OptionMenu 1, the entry in OptionMenu 2 changes. – Bakira Jan 09 '22 at 17:19
  • I think this post https://stackoverflow.com/questions/11801121/python-tkinter-optionmenu-add-a-command-to-multiple-optionmenus could be the answer, but to be honest, I'm not sure, how to implement the solution on my code. – Bakira Jan 09 '22 at 17:49
  • Yes, considered that possibility, but it looks to me like you're doing things right with respect to that the issue in that question. I still don't understand the relationship you want to have between the two `OptionMenu`s. – martineau Jan 09 '22 at 18:20
  • Actually, there should be no "relationship" between both OptionMenu's. I want to select a file individually for each OptionMenu. But when selecting a file in OptionMenu 1 the lambda function gets triggered and changes the OptionMenu 2, not OptionMenu 1. Please let me know, if I could clarify the issue. – Bakira Jan 09 '22 at 18:31
  • 1
    Because of the `self.active_file_selection = [self.view.file_one, self.view.file_two]` in the `update_file_list_selection()` method, you're essentially updating both of them with the same data. – martineau Jan 09 '22 at 19:40
  • Yes this is correct, but when changing OptionMenu 1, the change occur on OptionMenu 2, not on OptionMenu 1. Both OptionMenus have to show the same data (["File 1", "File 2", "File 3", "File 4"]), but I want to select on each OptionMenu a different entry. – Bakira Jan 09 '22 at 20:24
  • I could not reproduce that problem about changes of OptionMenu 1 occurring on #2. – martineau Jan 09 '22 at 20:25
  • Really? Because when I select "File 4" in OptionMenu 1, OptionMenu 2 will display "File 4", OptionMenu 1 has still "File 1" – Bakira Jan 09 '22 at 20:30
  • Finally, a clear explanation of how to reproduce the problem. I'll look into it. – martineau Jan 09 '22 at 20:49
  • At least part of the problem is because you're confusing an [`OptionMenu`](https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/optionmenu.html) with a [`Menu`](https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/menu.html). The former has no `add_command()` method that I know of. – martineau Jan 09 '22 at 21:24
  • @martineau: _"... you're confusing an OptionMenu with a Menu"_ - that's not quite correct. an `OptionMenu` is just a `MenuButton` and a `Menu`, combined with a custom callback. The OP is calling `add_command()` on the `Menu` associated with the `OptionMenu`. – Bryan Oakley Jan 10 '22 at 06:04
  • @Bryan: Thanks, obviously I didn't know that. Sigh, yet another poorly document tkinter "feature"… – martineau Jan 10 '22 at 07:37
  • @Bryan: After reading some of tkinter's source code I now understand how the OP managed to get access to the undocumented private `__menu` attribute `OptionMenu` widgets have in order to call its `add_command()` method. I had no idea they were hacking the widget like that — which seems surprising for someone who apparently didn't know about the common `lambda` values in `for` loops issue. – martineau Jan 10 '22 at 18:39
  • 1
    @martineau: the `OptionMenu` has always been a bit of an odd bird. Even in the tcl/tk world it's not a true widget, it's just a helper function that creates a standard menu and menubutton widget. In the tcl/tk version, `tk_optionMenu` returns the menu rather than the menu button, so it's kind-of expected to be able to tweak the menu. The tcl/tk version doesn't support the `command` option. – Bryan Oakley Jan 10 '22 at 18:52

1 Answers1

0

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.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Thank you! Just what I was looking for. It seems I need to learn more about lambda functions.... – Bakira Jan 10 '22 at 07:18
  • Bryan: There is a command associated with each item (the one you fixed). – martineau Jan 10 '22 at 07:30
  • @martineau: right, but there isn't a command associated with the `OptionMenu` as a whole. But you're right that the way I phrased the answer is incorrect. I'll fix it. – Bryan Oakley Jan 10 '22 at 16:21
  • Makes more sense now. I didn't even know that `OptionMenu`s supported a `command=` option until I recently stumbled across the fact doing some research on them. – martineau Jan 10 '22 at 16:51