pythontkinter

How to create a menu on every frame of a Tkinter application


I'm aware of how to create a basic Tkinter menu bar, but I'm not sure how to implement it such that the menu appears on every frame of a multi-frame GUI.

I will be using the menu bar to switch between frames. Therefore, I need to run the controller.show_frame command within the menu commands. I am currently using buttons to do this.

I'm unable to find a way to do this, as (as far as I am aware) the menu must be created in the frame class rather than the tk.Tk class, in order to allow me to run the function.

Here is the code:

""" Messing about with tkinter """
import tkinter as tk
LARGE_FONT = ("Verdana", 12)

class Window(tk.Tk):
    """ Main class """

    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames = {}

        for frame in (Main, Checker):
            current_frame = frame(container, self)
            self.frames[frame] = current_frame
            current_frame.grid(row=0, column=0, sticky="nsew")

        self.show_frame(Main)

    def show_frame(self, cont):
        """ Raises a particular frame, bringing it into view """

        frame = self.frames[cont]
        frame.tkraise()

def qprint(quick_print):
    """ Function to print a string """
    print(quick_print)

class Main(tk.Frame):
    """ Main frame of program """

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        label = tk.Label(self, text="Main Menu", font=LARGE_FONT)
        label.pack(pady=10, padx=10)

class Checker(tk.Frame):
    """ Password Strength Checker """

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        label = tk.Label(self, text="Password Checker", font=LARGE_FONT)
        label.pack(pady=10, padx=10)


APP = Window()
APP.geometry("350x200")
APP.mainloop()

Solution

  • "the menu must be created in the frame class rather than the tk.Tk class, in order to allow me to run the function."

    I don't think that's true, see below example that creates Menu for a Toplevel widget:

    import tkinter as tk
    
    
    if __name__ == '__main__':
        root = tk.Tk()
        root.withdraw()
    
        toplevel = tk.Toplevel(root)
    
        # create a toplevel menu
        menubar = tk.Menu(toplevel)
        menubar.add_command(label="Hello!")
        menubar.add_command(label="Quit!", command=root.quit)
    
        # display the menu
        toplevel.config(menu=menubar)
    
        root.mainloop()
    

    Alternatively, you can create menu's in frames for their parents with the condition that their parent is Toplevel-like.

    In below example when a menu item is selected Root's menu jumps between the Root's menu and its children FrameWithMenu object's menu:

    import tkinter as tk
    
    
    class Root(tk.Tk):
        def __init__(self):
            super().__init__()
    
            self.title("The Root class with menu")
            self.a_frame = FrameWithMenu(self)
            self.create_menu()
    
        def create_menu(self):
            self.menubar = tk.Menu(self)
            self.menubar.add_command(label="Root", command=self.a_frame.replace_menu)
            self['menu'] = self.menubar
    
    
    
    class FrameWithMenu(tk.Frame):
        def __init__(self, master):
            super().__init__(master)
    
        def replace_menu(self):
            """ Overwrite parent's menu if parent's class name is in _valid_cls_names.
            """
    
            _parent_cls_name = type(self.master).__name__
            _valid_cls_names = ("Tk", "Toplevel", "Root")
            if _parent_cls_name in _valid_cls_names:
                self.menubar = tk.Menu(self)
                self.menubar.add_command(label="Frame", command=self.master.create_menu)
                self.master['menu'] = self.menubar
    
    
    if __name__ == '__main__':
        root = Root()
        root.mainloop()