pythontkintermenubar

Python Tkinter error when calling method from menu bar option


I get the following error when running the display method defined in the Page1 class from the Display option on the menu bar:

 '_tkinter.tkapp' object has no attribute 'body_frm'

The method works correctly when run from the Display button in Page1.

I created an instance of Page1 called self.page1. This is used to call the method from the menu bar:

file_menu.add_command(label='Display', command=lambda: self.page1.display(self))

I think there may be something wrong here as I'm using lambda and display(self).

With just command=self.page1.display, I get the following error: '_tkinter.tkapp' object has no attribute 'page1'

With lambda: self.page1.display, I don't get an error but the method doesn't work With lambda: self.page1.display(), I get TypeError: display() missing 1 required positional argument: 'self'

The call from the page button uses command=self.display and works fine.

I can get the method to function both on the menu bar and page button by change self.body_frm to Page1.body_frm. While this works, I don't believe that's a proper implementation.

Code:

import tkinter as tk
from tkinter import ttk


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.title('Method Test')
        self.geometry('500x300')

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

        # Create menu bar
        self.create_menu_frame(self)

        # Create a dictionary of frames
        self.frames = {}

        self.page1 = Page1
        self.page2 = Page2

        # Add the two page frames to the dictionary.
        for page_frame in (self.page1, self.page2):
            frame = page_frame(container, self)
            self.frames[page_frame] = frame
            frame.grid(row=0, column=0, sticky=tk.NSEW)

        # Switch Order Entry frame
        self.show_frame(self.page1)

    def create_menu_frame(self, container: ttk.Frame) -> None:
        # Create menu bar
        menu_bar = tk.Menu(container)

        # Create options for File menu
        file_menu = tk.Menu(menu_bar, tearoff=0)
        file_menu.add_command(
            label='Display', command=lambda: self.page1.display(self))
        file_menu.add_separator()
        file_menu.add_command(
            label='Page 1', command=lambda: self.show_frame(self.page1))
        file_menu.add_command(
            label='Page 2', command=lambda: self.show_frame(self.page2))
        file_menu.add_separator()
        file_menu.add_command(label='Exit', command=self.quit)

        # Assign file menu list to the File option
        menu_bar.add_cascade(label="File", menu=file_menu)

        # Assign assign menu bar to the window
        container.config(menu=menu_bar)

    def show_frame(self, cont: tk.Frame) -> None:
        frame = self.frames[cont]
        # Raises the specified frame to the top
        frame.tkraise()


class Page1(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        Page1.result_str = tk.StringVar(value='')

        # Create page frame sections
        self.header_frm = tk.Frame(self)
        self.header_frm.pack(side=tk.TOP)
        self.header_frm.grid_rowconfigure(0, weight=1)

        self.body_frm = tk.Frame(self)
        self.body_frm.pack()
        self.body_frm.grid_rowconfigure(0, weight=1)

        self.footer_frm = tk.Frame(self)
        self.footer_frm.pack()
        self.footer_frm.grid_rowconfigure(0, weight=1)

        # Header Section
        label = tk.Label(self.header_frm, text='This is the Main Page')
        label.pack(pady=10, padx=10)

        # footer Section
        close_btn = tk.Button(self.footer_frm, text='Close', command=quit)
        close_btn.grid(row=1, column=1, padx=5, pady=5)

        display_btn = tk.Button(
            self.footer_frm, text='Display', command=self.display)
        display_btn.grid(row=1, column=2, padx=5, pady=5)

        switch_window_btn = tk.Button(
            self.footer_frm, text='Page 2',
            command=lambda: controller.show_frame(controller.page2)
        )
        switch_window_btn.grid(row=1, column=0, padx=5, pady=5)

    def display(self):
        label = tk.Label(
            self.body_frm,
            text='Display method works')
        label.grid(row=2, column=0, columnspan=2, padx=15, pady=5)


class Page2(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        label = tk.Label(self, text='Second Page')
        label.pack(padx=10, pady=10)

        switch_window_button = tk.Button(
            self,
            text='Return to Page 1',
            command=lambda: controller.show_frame(controller.page1),
        )
        switch_window_button.pack(side='bottom', fill=tk.X)


def main():
    prog_app = App()
    prog_app.mainloop()


if __name__ == '__main__':
    main()

Solution

  • Correct code

    command=lambda: self.frames[self.page1].display()
    

    or

    command=lambda: self.frames[Page1].display()
    

    self.page1 is not instance of class but only alias for Page1.

    Real instance is in self.frames[self.page1] (or self.frames[Page1])