pythonuser-interfacetkintertk-toolkittkinter-menu

Is there a way to prevent a ttk OptionMenu callback from firing twice?


I have a tkinter application with a ttk.OptionMenu whose value is set to a tk.StringVar. I would like to get a callback whenever the user picks a new option in the option menu. After registering a callback on the StringVar, whenever the user changes the option menu selection, the callback is fired twice. Is there any way to have the callback fire exactly once when the user makes a change to the menu?

Here's the complete (runnable) example:

import tkinter as tk
from tkinter import ttk


OPTIONS = ["A", "B", "C"]

# Set up a new window
root = tk.Tk()

# Create a variable
option_var = tk.StringVar()

# Register a callback that fires when the variable is changed
option_var.trace("w", lambda *args: print("option_var is", option_var.get(), "with args", args))

# Create the OptionMenu and add it to the GUI root
ttk.OptionMenu(root, option_var, OPTIONS[0], *OPTIONS).pack()

# Run the application
root.mainloop()

As expected, on startup, the callback fires once to reflect the change to the default variable:

option_var is A with args ('PY_VAR0', '', 'w')

However, if a user clicks the dropdown and selects B, the callback is fired twice.

option_var is B with args ('PY_VAR0', '', 'w')
option_var is B with args ('PY_VAR0', '', 'w')

Is there a way to set up the callback such that it only fires once when the option menu changes? Alternatively, is there some way to separate out one of these callbacks from the other so that I can take an action once per menu change?

(Example tested with CPython 3.6.8 running on Windows 10 1809)


Solution

  • There's no need to use trace here, the Optionmenu has a command argument:

    import tkinter as tk
    from tkinter import ttk
    
    OPTIONS = ["A", "B", "C"]
    
    root = tk.Tk()
    
    def callback(value):
        print("option_var is", value)
    
    option_var = tk.StringVar()
    ttk.OptionMenu(root, option_var, OPTIONS[0], *OPTIONS, command=callback).pack()
    
    root.mainloop()