How to access and/or modify input values from GUI elements in other class with Python Tkinter

Question:

I am working on a Tkinter project where I have multiple input/entry frames. The entry frames are placed in a container frame within the main GUI class. Based on selections in the main GUI class, the a frame is raised to be visible using tkraise(). The entry frames have multiple labels entry boxes etc., so defining them under the main GUI class (tk.Tk) within the same .py file makes the code unmanageable. Instead the entry frames are defined in separate classes within the same .py file (later I want to place them in separate .py files as well). I want to obtain inputs from the entry frames (usually only the active/visible one) and make calculations with them in the main GUI class and then print them at some other place defined in the main GUI.

I would like to be able to write data to the entries in the specific input frame from the main GUI class, and also be able to read the current entry values from the main GUI class.

My original code is large so I created a minimal reproducible example below.

What is the best, most efficient way to access and modify input values from GUI element in another class? What is the correct way to handle this kind of multi-class, multi-file GUI projects where one needs to obtain and modify data from various GUIS defined in separate classes and files.

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")


        # 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")



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



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

    def __init__(self, parent, controller):

        # 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)
                    self.blue_circle_area = math.pi*self.radius**2
                    # print(f"Radius: {self.radius}. Area: {self.blue_circle_area:.2f}")
                except ValueError:
                    self.Entry_var_radius.set("")
                    print(f"Warning! Floating point number only!")

        # Inıtialize variable.
        self.blue_circle_area = 0

        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.radius = tk.Entry(self, font=font_1, textvariable=self.Entry_var_radius)
        self.radius.grid(row=1, column=1)
        self.radius = self.Entry_var_radius.get()


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

    def __init__(self, parent, controller):

        # 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()

Edit after accepted answer. The modified code is here:

import tkinter as tk
import math


class Frame1(tk.Frame):

    def __init__(self, parent):
        super().__init__()

        # 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)
                    self.blue_circle_area = math.pi*self.radius**2
                    self.parent.label_main_frame.config(text = f"Main frame. Value: {self.radius}")
                except ValueError:
                    self.Entry_var_radius.set(f"{self.radius}")
                    print(f"Warning! Floating point number only!")


        self.parent = parent
        self.label = tk.Label(self, text='Frame 1. Entry:' )
        self.label.grid(row=0, 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.radius = tk.Entry(self, textvariable=self.Entry_var_radius)
        self.radius.grid(row=0, column=1)
        self.radius = self.Entry_var_radius.get()


class Frame2(tk.Frame):

    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.frame2_variable = 0.
        self.button = tk.Button(self, text=f"Frame 2. Set to zero!", command=self.frame2_method)
        self.button.grid(row=0, column=0)

    def frame2_method(self):
        self.parent.frame1.Entry_var_radius.set("0")



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

        self.frame1 = Frame1(self)
        self.frame2 = Frame2(self)

        self.frame1.grid(row=0, column=0, sticky="W")
        self.frame2.grid(row=1, column=0, sticky="W")

        self.label_main_frame = tk.Label(self, text='Main frame.' )
        self.label_main_frame.grid(row=2, column=0, sticky="W")


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

||

Answers:

Here is a structure that should serve you.

You have one frame that serves as a container for your other frames, the other frames being attributes of this parent frame have their own attributes or widgets exposed to each other via the parent frame.

import tkinter as tk

class Frame1(tk.Frame):

    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.button = tk.Button(self, text='Frame 1 Button', command=self.frame1_method)
        self.button.grid(row=1, column=1)

    def frame1_method(self):
        # get frame2 button text
        print(self.parent.frame2.button['text'])
        # set frame2 button text
        self.parent.frame2.button['text'] = 'Frame 2 Button Edited'


class Frame2(tk.Frame):

    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.button = tk.Button(self, text='Frame 2 Button', command=self.frame2_method)
        self.button.grid(row=1, column=1)

    def frame2_method(self):
        # get frame1 button text
        print(self.parent.frame1.button['text'])
        # set frame1 button text
        self.parent.frame1.button['text'] = 'Frame 1 Button Edited'


class MainFrame(tk.Frame):
    def __init__(self, parent):
        super().__init__()

        self.frame1 = Frame1(self)
        self.frame2 = Frame2(self)

        self.frame1.grid(row=1, column=1)
        self.frame2.grid(row=2, column=1)


root = tk.Tk()
MainFrame(root).grid(row=1, column=1)
root.mainloop()
Answered By: Samuel Kazeem