Search code examples
pythonclassooptkinter

What is the best way to access object of subclass in other subclass?


Attached is a minimum working example of a problem I have:

class A():
    def __init__(self, *args, **kwargs):
        
        self.b=B(self)
        self.c=C(self)
        
        
class B():
    def __init__(self, parent):
    
        self.a1=3
    
class C():
    def __init__(self, parent):
        
        self.a2=4
        
        print(B.a1)
    
    def ab():
        print(B.a1)

C(object).ab()

What is the best way to access in class C the object of class B?

In principle, I built a TKinter App with every frame being a class and I want in one frame to access the variable of another frame.

EDIT @all thank you all fpr the input. I expanded the minimum working example to my specific problem:

import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
from tkinter import messagebox


class main_window(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        self.title("xxx")
        self.geometry("800x1000")
        
        #Build GUI
        self.inputframe = INPUTframe(self)
        self.imregframe = IMREGframe(self)
        self.calcframe = CALCframe(self)

class INPUTframe(tk.Frame):
    def __init__(self, parent, width=800, height=150):
        tk.Frame.__init__(self, parent, width=width, height=height)
        self.place(x=0, y=0)
        
        label= ttk.Label(self, text="xxx", font=('Arial',12))
        label.place(x=10,y=0)

        self.create_widgets()
        
    def create_widgets(self):
        
        label1 = tk.Label(self, text="xxx", font=('Arial',10))
        label1.place(x=30,y=30)
        button1 = tk.Button(self, text='xxx',command = lambda: self.path(1))
        button1.place(x=100,y=30)
        self.textentry1=tk.StringVar()
        self.entry1 = tk.Entry (self, textvariable= self.textentry1)
        self.entry1.place(x=160,y=30,width="350")
        label11 = tk.Label(self, text="xxx", font=('Arial',10))
        label11.place(x=500,y=30)
        
    def path(self,var):

        match var:
           case 1:
               self.entry1.delete(0, 'end')
               filename = filedialog.askopenfilename(parent=self,title='Choose a file')
               self.entry1.insert("end",filename)
               return filename

class IMREGframe(tk.Frame):
    def __init__(self, parent, width=250, height=150):
        tk.Frame.__init__(self, parent, width=width, height=height)
        self.place(x=0,y=150)

        label4 = tk.Label(self, text="xxx", font=('Arial',12))
        label4.place(x=10,y=0)
        
        self.SFAtrue=tk.BooleanVar(value=True)
        
        self.create_widgets()      
        
    def create_widgets(self):
        #self.SFAtrue=tk.BooleanVar(value=True)
        checkb1 = tk.Checkbutton(self, text='xxx', variable=self.SFAtrue, onvalue=0, offvalue=1)
        checkb1.place(x=30,y=30)
        checkb2 = tk.Checkbutton(self, text='xxx', variable=self.SFAtrue, onvalue=1, offvalue=0)
        checkb2.place(x=30,y=50)
 

class CALCframe(tk.Frame):
    def __init__(self, parent, width=800, height=100):
        tk.Frame.__init__(self, parent, width=width, height=height)
        self.place(x=0,y=370)
        self.parent=parent
        
        self.create_widgets()
        
    
    def create_widgets(self):
        
        button4 = tk.Button(self, text='xxx', command = lambda: self.calculate(self.parent.imregframe.SFAtrue.get())) #bester Weg um Variabele aus anderer Klasse/Objekt zu holen?
        button4.config( height = 4, width = 12 )
        button4.place(x=50,y=0)

        self.Previewtrue=tk.BooleanVar(value=False)
        checkb3 = tk.Checkbutton(self, text='xxx',variable=self.Previewtrue, onvalue=1, offvalue=0)
        checkb3.place(x=150,y=0)
        label5 = tk.Label(self,text="xxx", font=("Arial", 9))
        label5.place(x=150,y=30)
        label51 = tk.Label(self,text="xxx", font=("Arial", 9))
        label51.place(x=150,y=50)
        
    def calculate (self, calcType):
        
        print(calcType)
        print(self.parent.inputframe.textentry1.get())
    
main_window().mainloop()  

The thin I was missing was self.parent=parent as suggested by @Obaskly and it works now. Is this a good way to do it? The variables get quite long: self.parent.inputframe.textentry1.get()


Solution

  • There are two things wrong with your code:

    1. You are trying to access B.a1, which is incorrect because a1 is an instance variable, not a class variable. You should access it through the instance of B, which is held by A.

    2. The ab() method inside C should accept self as the first parameter to be an instance method.

    You need to maintain a reference to the B instance created in the A class.

    It should look like this, so that the C class can access the B instance and its attributes via its parent A.

    class A():
        def __init__(self, *args, **kwargs):
            self.b = B(self)
            self.c = C(self)
    
    
    class B():
        def __init__(self, parent):
            self.a1 = 3 
    
    
    class C():
        def __init__(self, parent):
            self.parent = parent  # Reference to the parent class
            self.a2 = 4
            print(self.parent.b.a1)  # Access B's a1 via the parent
    
        def ab(self):
            print(self.parent.b.a1) 
    
    
    # Create an instance of A
    instance_of_A = A()
    
    # Access method ab() of class C through instance of A
    instance_of_A.c.ab()
    

    EDIT

    The variable names can become quite long, that's for sure. There are few techniques you can use. Like using references, for example you can create shortcut references to frequently accessed widgets or variables.

    class CALCframe(tk.Frame):
        def __init__(self, parent, width=800, height=100):
            # ...
            
            # Shortcut references
            self.imregframe = self.parent.imregframe
            self.inputframe = self.parent.inputframe
            
            self.create_widgets()
            
        def create_widgets(self):
            button4 = tk.Button(self, text='xxx', command=lambda: self.calculate(self.imregframe.SFAtrue.get()))
            button4.config(height=4, width=12)
            button4.place(x=50, y=0)
            
            # ...
            
        def calculate(self, calcType):
            print(calcType)
            print(self.inputframe.textentry1.get())
    

    Or you can use a controller Class; that holds the state of your application and allows frames to interact through it.

    class AController:
        def __init__(self):
            self.main_window = main_window(self)
            
        def get_SFAtrue(self):
            return self.main_window.imregframe.SFAtrue.get()
        
        def get_textentry1(self):
            return self.main_window.inputframe.textentry1.get()
    
    class main_window(tk.Tk):
        def __init__(self, controller, *args, **kwargs):
            tk.Tk.__init__(self, *args, **kwargs)
            self.controller = controller
            self.title("xxx")
            self.geometry("800x1000")
            
            self.inputframe = INPUTframe(self)
            self.imregframe = IMREGframe(self)
            self.calcframe = CALCframe(self)
    
    class CALCframe(tk.Frame):
        def __init__(self, parent, width=800, height=100):
            tk.Frame.__init__(self, parent, width=width, height=height)
            self.controller = parent.controller
            
            self.create_widgets()
            
        def create_widgets(self):
            button4 = tk.Button(self, text='xxx', command=self.calculate)
            button4.config(height=4, width=12)
            button4.place(x=50, y=0)
            
        def calculate(self):
            calcType = self.controller.get_SFAtrue()
            textEntry1 = self.controller.get_textentry1()
            print(calcType)
            print(textEntry1)
    
    AController()