Search code examples
pythontkinter

How to execute a command when clicking on a tkinter menu, not a menu option?


I am trying to make a tkinter GUI that will allow the user to select a graph to display. I want this graph to be displayed when the user clicks the tkinter menu, and then if the user selects any of the menu options parts of the graph will be removed/added.

For example, in the image below, I want a graph to be displayed when the user clicks "Velocity", even if they don't click any of the options below it, "u", "v" etc.

Screenshot of mouse hoovering over a tkinter menu

Here is my code. As you can see I tried using postcommand, but I think I misunderstood what it does, as it doesnt work the way I want.

import tkinter as tk
from tkinter import ttk


class App(tk.Tk):
    def __init__(self):
        # Initial setup
        super().__init__()
        self.title('Class based reader')
        self.minsize(800,600)

        # Widgets
        self.option_add('*tearOff', False)
        menu_bar = tk.Menu(self)
        self.config(menu = menu_bar)
        
        velocity_menu = tk.Menu(self, postcommand = self.do_something())
        menu_bar.add_cascade(label='Velocity', menu = velocity_menu)
        
        velocity_dict= {
            'u': tk.StringVar(),
            'v': tk.StringVar(),
            'du': tk.StringVar(),
            'dv': tk.StringVar()
        }
        for var, check in velocity_dict.items():
            velocity_menu.add_checkbutton(label = var, variable = check, onvalue = 1, offvalue = 0)

        flux_menu = tk.Menu(self, postcommand = self.do_something2())
        menu_bar.add_cascade(label='Momentum Flux', menu = flux_menu)
        flux_dict= {
            'uu': tk.StringVar(),
            'uv': tk.StringVar(),
            'vu': tk.StringVar(),
            'vv': tk.StringVar()
        }
        for var, check in flux_dict.items():
            flux_menu.add_checkbutton(label = var, variable = check, onvalue = 1, offvalue = 0)

        pressure_menu = tk.Menu(self)
        menu_bar.add_cascade(label='Pressure', menu = pressure_menu)

        force_menu = tk.Menu(self)
        menu_bar.add_cascade(label='Force', menu = force_menu)

        analytic_menu = tk.Menu(self)
        menu_bar.add_cascade(label='Compare Analytic', menu = analytic_menu)
        
        # Run the app
        self.mainloop()
    def do_something(self):
        print("something")

    def do_something2(self):
        print("something2")


App()

Solution

  • The problem in your code arises from how you’ve utilized the postcommand parameter during the initialization of the Menu widget. Instead of passing function references, you’re directly invoking the functions.

    I would try it like this:

    import tkinter as tk
    from tkinter import ttk
    
    
    class App(tk.Tk):
        def __init__(self):
            # Initial setup
            super().__init__()
            self.title('Class based reader')
            self.minsize(800,600)
    
            # Widgets
            self.option_add('*tearOff', False)
            menu_bar = tk.Menu(self)
            self.config(menu=menu_bar)
            
            velocity_menu = tk.Menu(self)
            velocity_menu.add_command(label='Velocity', command=self.do_something)
            menu_bar.add_cascade(label='Velocity', menu=velocity_menu)
            
            velocity_dict = {
                'u': tk.StringVar(),
                'v': tk.StringVar(),
                'du': tk.StringVar(),
                'dv': tk.StringVar()
            }
            for var, check in velocity_dict.items():
                velocity_menu.add_checkbutton(label=var, variable=check, onvalue=1, offvalue=0)
    
            flux_menu = tk.Menu(self)
            flux_menu.add_command(label='Momentum Flux', command=self.do_something2)
            menu_bar.add_cascade(label='Momentum Flux', menu=flux_menu)
            flux_dict = {
                'uu': tk.StringVar(),
                'uv': tk.StringVar(),
                'vu': tk.StringVar(),
                'vv': tk.StringVar()
            }
            for var, check in flux_dict.items():
                flux_menu.add_checkbutton(label=var, variable=check, onvalue=1, offvalue=0)
    
            pressure_menu = tk.Menu(self)
            menu_bar.add_cascade(label='Pressure', menu=pressure_menu)
    
            force_menu = tk.Menu(self)
            menu_bar.add_cascade(label='Force', menu=force_menu)
    
            analytic_menu = tk.Menu(self)
            menu_bar.add_cascade(label='Compare Analytic', menu=analytic_menu)
            
            # Run the app
            self.mainloop()
    
        def do_something(self):
            print("Velocity menu clicked")
    
        def do_something2(self):
            print("Momentum Flux menu clicked")
    
    
    App()
    

    Removed 'postcommand' parameter from the 'Menu' widget initialization because it's not necessary for this case.

    Added 'command' parameter in 'add_command' for each menu item to call the respective functions when the menu item is clicked.