How to reach and modify child class's variables from parent class when child class is one of many Tkinter frames made visible container and tkraise()

Question:

I am writing a Python GUI application with Tkinter where I have several frames that I want to manage separately. I want to put these "child" frames in separate classes (and ultimately in different files) to make the overall code more manageable. Each child class is basically a Tkinter frame with input elements. Based on selections from the main GUI, the relevant child class frame is shown. This is achieved using container and tkraise(). I want to reach child class variables from the main class but I cannot with my current code which is given below. I believe there is a problem with the initialization of child classes and/or the inheritance scheme of my app.

What is the correct way to structure a Python application in a setting where you have child classes being shown with container and tkraise() scheme and you want to reach child class variables form the main class? I appreciate your help.

import tkinter as tk
from tkinter import ttk
import math

 

# Padding values.
tab_padx = (10, 0)
tab_pady = (20, 0)

# Font settings.
font_1 = ("Arial", 13, "bold")

# Main class.
class Main_GUI(tk.Tk):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.title("DEMO")

        self.frame_blue_circle = BlueCircle(self, self)
        self.frame_green_square = GreenSquare(self, self)


        # Available shapes.
        self.available_shapes = ["CIRCLE", "SQUARE"]
        # Available colors.
        self.available_colors = ["BLUE", "GREEN"]

        # Function to run when color is changed.
        def color_change(*args):
            self.color = self.option_var_color.get()
            if self.color == "BLUE" and self.shape == "CIRCLE":
                self.type = "BlueCircle"
                self.show_frame("BlueCircle")
            elif self.color == "GREEN" and self.shape == "SQUARE":
                self.show_frame("GreenSquare")
            else:
                self.show_frame("Unimplemented")
            print(f"{self.color} {self.shape}")

        # Function to run when shape is changed.
        def shape_change(*args):
            self.shape = self.option_var_shape.get()
            if self.color == "BLUE" and self.shape == "CIRCLE":
                self.show_frame("BlueCircle")
            elif self.color == "GREEN" and self.shape == "SQUARE":
                self.show_frame("GreenSquare")
            else:
                self.show_frame("Unimplemented")
            print(f"{self.color} {self.shape}")


        #GUI tabs
        self.nb = ttk.Notebook(self)
        self.nb.grid(row=1, column=0, sticky="w", padx=10, pady=10)

        #GUI tab1 - Type selection.
        self.tab1 = tk.Frame(self.nb)
        self.nb.add(self.tab1, text="Type")

        #GUI tab2 - Unput for selected type.
        self.tab2 = tk.Frame(self.nb)
        self.nb.add(self.tab2, text="Input")

        #GUI tab3 - Calculate result for selected type with its specific inputs.
        self.tab3 = tk.Frame(self.nb)
        self.nb.add(self.tab3, text="Result")

        # Tab-1 types.
        # Shapes.
        self.Label_shape = tk.Label(self.tab1, text = "Shape: ", font=font_1)
        self.Label_shape.grid(row=10, column=0, padx=tab_padx, pady=tab_pady, sticky="W")

        # Setup variable for disk type dropdown menu.
        self.option_var_shape= tk.StringVar()
        self.option_var_shape.set(self.available_shapes[0])
        self.option_var_shape.trace("w", shape_change)
        self.shape = self.option_var_shape.get()

        self.shape_dropdown_menu = tk.OptionMenu(self.tab1, self.option_var_shape, *self.available_shapes)
        self.shape_dropdown_menu.grid(row=10, column=1, sticky="WE", padx=tab_padx, pady=tab_pady)
        self.shape_dropdown_menu.config(font=font_1, width=20)
        self.shape_dropdown_menu["menu"].config(font=font_1)


        # Colors.
        self.Label_color = tk.Label(self.tab1, text = "Color: ", font=font_1)
        self.Label_color.grid(row=20, column=0, padx=tab_padx, pady=tab_pady, sticky="W")

        # Setup variable for disk type dropdown menu.
        self.option_var_color= tk.StringVar()
        self.option_var_color.set(self.available_colors[0])
        self.option_var_color.trace("w", color_change)
        self.color = self.option_var_color.get()

        self.color_dropdown_menu = tk.OptionMenu(self.tab1, self.option_var_color, *self.available_colors)
        self.color_dropdown_menu.grid(row=20, column=1, sticky="WE", padx=tab_padx, pady=tab_pady)
        self.color_dropdown_menu.config(font=font_1, width=20)
        self.color_dropdown_menu["menu"].config(font=font_1)


        # Tab-2. Show frame based on selection in Tab-1.
        # Container for frames.
        container = tk.Frame(self.tab2)
        container.grid(row=0, column=0)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames = {}
        for F in (BlueCircle, GreenSquare, Unimplemented):
            page_name = F.__name__
            frame = F(parent=container, controller=self)
            self.frames[page_name] = frame
            frame.grid(row=0, column=0, sticky="nsew")

        self.show_frame("BlueCircle")


        # Tab-3. Calculate and display result based on Tab-1 and Tab-2.
        # Label to display result.
        result_text = "Result will be displayed here."
        self.Label_result = tk.Label(self.tab3, text = result_text, font=font_1, fg="RED")
        self.Label_result.grid(row=10, column=0, padx=tab_padx, pady=tab_pady, sticky="W")

        self.button = tk.Button(self.tab3, text=f"Print", command=self.print_info)
        self.button.grid(row=20, column=0, sticky="W")

        # print(self.Label_result)



    def show_frame(self, page_name):
        frame = self.frames[page_name]
        frame.tkraise()

    def print_info(self):
        bc_text = f"Blue circle radius: {self.frame_blue_circle.radius}"
        print(bc_text)



# Class defining GUI for BlueCircle.
class BlueCircle(tk.Frame):

    def __init__(self, parent, controller, *args, **kwargs):

        super().__init__(*args, **kwargs)

        self.parent = parent
        self.radius = 0

        # Function to run when rim radius is changed.
        def Entry_change(*args):
            value = self.Entry_var_radius.get()
            
            if value == "":
                self.Entry_var_radius.set(".0")
            else:
                try:
                    self.radius = float(value)
                    print(self.radius)
                except ValueError:
                    self.Entry_var_radius.set("")
                    print(f"Warning! Floating point number only!")


        tk.Frame.__init__(self, parent)
        self.controller = controller
        self.label = tk.Label(self, text="Blue Circle", font=font_1, fg="BLUE")
        self.label.grid(row=0, column=0)
        self.label = tk.Label(self, text="Radius:")
        self.label.grid(row=1, column=0)

        # Setup variable for entry to use in callback trace.
        self.Entry_var_radius = tk.StringVar()
        self.Entry_var_radius.trace("w", lambda name, index, mode, sv=self.Entry_var_radius: Entry_change(self.Entry_var_radius))
        # Entry.
        self.Entry_radius = tk.Entry(self, font=font_1, textvariable=self.Entry_var_radius)
        self.Entry_radius.grid(row=1, column=1)
        self.radius = self.Entry_radius.get()


 
# Class defining GUI for GreenSquare.
class GreenSquare(tk.Frame):

    def __init__(self, parent, controller):

        super().__init__()
        self.parent = parent

        # Function to run when rim radius is changed.
        def Entry_change(*args):
            value = self.Entry_var_lenght.get()
            if value == "":
                self.Entry_var_lenght.set(".0")
            else:
                try:
                    self.lenght = float(value)
                    self.green_square_area = self.lenght**2
                    # print(f"Side lenght: {self.lenght}. Area: {self.green_square_area:.2f}")
                except ValueError:
                    self.Entry_var_lenght.set("")
                    print(f"Warning! Floating point number only!")


        # Inıtialize variable.
        self.green_square_area = 0


        tk.Frame.__init__(self, parent)
        self.controller = controller
        self.label = tk.Label(self, text="Green Squire", font=font_1, fg="GREEN")
        self.label.grid(row=0, column=0)
        self.label = tk.Label(self, text="Side lenght:")
        self.label.grid(row=1, column=0)

        # Setup variable for entry to use in callback trace.
        self.Entry_var_lenght = tk.StringVar()
        self.Entry_var_lenght.trace("w", lambda name, index, mode, sv=self.Entry_var_lenght: Entry_change(self.Entry_var_lenght))
        # Entry.
        self.lenght = tk.Entry(self, font=font_1, textvariable=self.Entry_var_lenght)
        self.lenght.grid(row=1, column=1)
        self.lenght = self.Entry_var_lenght.get()


# Class defining GUI for unimplemented options.
class Unimplemented(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        self.label = tk.Label(self, text="To be implemented...", font=font_1, fg="RED")
        self.label.grid(row=0, column=0)



if __name__ == "__main__":
    app = Main_GUI()
    app.mainloop()
Asked By: smoothy

||

Answers:

In your __init__() method you have: self.frame_blue_circle = BlueCircle(self, self). This means that anywhere in your Main_GUI class (I mean typically other methods) you can access this and then its radius attribute.

In fact, you already do this here:

    def print_info(self):
        bc_text = f"Blue circle radius: {self.frame_blue_circle.radius}"
        print(bc_text)
Answered By: quamrana

Note that self.frame_blue_circle is not the instance of BlueCircle shown inside the notebook, so self.frame_blue_circle.radius is not the one input inside the "Input" tab.

The correct instance should be self.frames['BlueCircle'], so you need to use self.frames['BlueCircle'].radius instead:

def print_info(self):
    bc_text = f"Blue circle radius: {self.frames['BlueCircle'].radius}"
    print(bc_text)
Answered By: acw1668