Is there a way to execute a function ONLY if the option of a tkinter optionmenu is changed?

86 Views Asked by At

When using tkinter OptionMenu the command input lets you activate a function, but the same function activates even when you click on the already set option. Example: option "A" is set, you click again on option "A" the command activates, however I would like it to happen only if I change the selection from "A" to another option that isn't "A" like "B" or "C" and viceversa.

Here's my code:

import tkinter 
from tkinter import *

# Create window------------------------------------------
window = tkinter.Tk()
window.title("Window")
window.geometry("800x500")
#--------------------------------------------------------

# Function-----------------------------------------------
def print_message(*args):
    print("the option has been changed")
#--------------------------------------------------------

# Dropdown menu------------------------------------------
# Datatype
option_selected = StringVar()  
# Create dropdown menu
option_list = ["A", "B", "C"]
option_selected.set(option_list[0]) 
option_menu = OptionMenu(window, option_selected, *option_list)
option_menu.pack()
option_selected.trace("w",print_message)
#--------------------------------------------------------

# Maintain window open
window.mainloop()
2

There are 2 best solutions below

4
acw1668 On BEST ANSWER

Basically OptionMenu does not support such feature, however you can create a custom OptionMenu to provide such feature:

import tkinter as tk

class CustomOptionMenu(tk.OptionMenu):
    def __init__(self, master, var, *values, command=None):
        super().__init__(master, var, *values,
                         command=self.on_selected if command else None)
        self.command = command
        self.value = var.get()

    def on_selected(self, value):
        # execute callback only if selected value is changed
        if self.value != value:
            self.command(value)
            self.value = value  # save the selected value

# Create window------------------------------------------
window = tk.Tk()
window.title("Window")
window.geometry("800x500")
#--------------------------------------------------------

# Function-----------------------------------------------
def print_message(value):
    print(f"the option has been changed to {value}")
#--------------------------------------------------------

# Dropdown menu------------------------------------------
# Datatype
option_selected = tk.StringVar()
# Create dropdown menu
option_list = ["A", "B", "C"]
option_selected.set(option_list[0])
option_menu = CustomOptionMenu(window, option_selected, *option_list, command=print_message)
option_menu.pack()
#--------------------------------------------------------

# Maintain window open
window.mainloop()
0
TheLizzard On

For completeness sake, here is how to do the same thing as @acw1668 but with a function decorator:

import tkinter 
from tkinter import *

def only_call_on_arg_change(first):
    def wrapper(command):
        prev_value = first
        def inner(value):
            nonlocal prev_value
            if value != prev_value:
                command(value)
                prev_value = value
        return inner
    return wrapper

# Create window------------------------------------------
window = tkinter.Tk()
window.title("Window")
window.geometry("800x500")
#--------------------------------------------------------

# Function-----------------------------------------------
@only_call_on_arg_change(first="A")
def print_message(*args):
    print("the option has been changed")
#--------------------------------------------------------

# Dropdown menu------------------------------------------
# Datatype
option_selected = StringVar()  
# Create dropdown menu
option_list = ["A", "B", "C"]
option_selected.set(option_list[0]) 
option_menu = OptionMenu(window, option_selected, *option_list, command=print_message)
option_menu.pack()
#--------------------------------------------------------

# Maintain window open
window.mainloop()

I used command=... when creating the OptionMenu, I store the last value, and I only call command if the value changed like @acw1668. The reason the only_call_on_arg_change function is messy is because unlike @acw1668's solution, I don't have access to the StringVar and can't get the default value with var.get(). If you stare at @acw1668's solution and mine side by side for long enough, you should be able to see the similarities and figure out how both approaches work.