How to Access Variables from Different Classes in Tkinter

How to access variables from different classes in tkinter?

At its core, your question has a simple answer. "How do I get a value from object X?" The answer is the same for any object: you get it by asking object X. All you need in order to do that is get a reference to the object and then access the attribute directly.

Accessing data from other pages

In your case, the code in PageTwo needs a reference to PageOne so you can get the v variable.

So, how do you get a reference? The code (which you copied either from a tutorial, or from the stackoverflow answer that the tutorial copied from) was designed to make this easy. Each page is given a reference to a controller, and this controller has a reference to each page. You can therefore ask the controller to give you a reference to a page.

The first step is to save the reference to the controller in each class. Interestingly, you're already doing this in PageOne, but you should do it in all the pages. Make sure you add self.controller = controller in every __init__ method, like so:

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

Next, we need to add a method in the controller class that will return a reference to the page. Add the following function to SampleApp:

class SampleApp(tk.Tk):
...
def get_page(self, page_class):
return self.frames[page_class]
...

Now, from within any "page" you can get access to the object for any other "page". For example, in PageTwo you can access the v variable from PageOne like this:

page1 = self.controller.get_page(PageOne)
page1.v.set("Hello, world")

Using shared data

An even better solution is for your SampleApp class to create a single set of variables that all of the pages share. You can create a dictionary in that class, and then use the controller to give every page access. For example:

class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.shared_data = {
"username": tk.StringVar(),
"password": tk.StringVar(),
...
)

Then, from within any class you can access the data like this:

entry1 = tk.Entry(self, textvariable=self.controller.shared_data["username"])
...
username = self.controller.shared_data["username"].get()

The reason this is the better solution is that your pages don't have to know how the other pages are implemented. When a page relies on the exact implementation of another page this is called tight coupling. If the pages don't need to know how the other pages are implemented, this is called loose coupling.

Loose coupling gives you more flexibility. Instead of having every page tightly coupled to every other page, they are all tightly coupled to a single object: the controller. As long as every page knows only about the controller, each page is free to be changed at any time without affecting the rest of the program.

Of course, if you change the controller you have to change all of the pages, but if you do a good job designing the controller that's less likely to occur and easier to manage when it does occur.

Retrieving variable from another class

The culprit is that you should not share
class member variables that way.

If different classes share some common
data, that data is probably another class
and they can inherit from it.

class CommonData():
client = 100

class A(CommonData):
def __init__(self):
print(A.client)

class B(CommonData):
def __init__(self):
print(B.client)

a = A()
b = B()

CommonData.client = 300

print(a.client)
print(b.client)

In above case every instance of A and every instance of B
share all the CommonData class variables, like client.

CommonData.client = 400

class C():
pass

You can use multiple inheritance too.
define all common data as CommonData attributes
and use CommonData as a class to hold data, like
in above example, don't create instances from it:

class D(C, CommonData):
def __init__(self):
print(D.client)

c = C()
d = D()

A simpler option would be to just define
a variable CommonData in the outer scope and
use it from anywhere:

common_data = 500

class A():
def __init__(self):
global common_data
print(common_data)
common_data = 200
# ...

But global variables are generally seen as a bad thing in a program as their use can become a problem for several reasons.

Yet another way is to pass the variable to the object initializer.
That makes the instance to keep its own value copied from
the creation value:

common_data = 600

class A():
def __init__(self, data):
self.common = data
print(self.common)

a = A(common_data)
common_data = 0
print(a.common)

If you run all the code above it will print

100
100
300
300
400
600
600

Edit:

See my comment to your answer and a simple example here.
Here I opt for two global references to tkinter StringVars.
The stringvars exist themselves in the Tk() namespace, like the
widgets; besides they are global Python names.

import tkinter as tk
from tkinter import ttk

class Page1(tk.Toplevel):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.title('Page1')
self.label1 = ttk.Label(self, text='Filename:')
self.entry1 = ttk.Entry(self, textvariable=input_file1)
self.label1.pack(side=tk.LEFT)
self.entry1.pack()

class Page2(tk.Toplevel):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.title('Page2')
self.label1 = ttk.Label(self, text='Filename:')
self.entry1 = ttk.Entry(self, textvariable=input_file2)
self.button1 = ttk.Button(self, text='Copy Here', command=copy_filename)
self.label1.pack(side=tk.LEFT)
self.entry1.pack(side=tk.LEFT)
self.button1.pack()

def copy_filename():
input_file2.set(input_file1.get())

root = tk.Tk() # has to exist for the StringVars to be created
root.iconify()
input_file1 = tk.StringVar()
input_file2 = tk.StringVar()

page1 = Page1(root)
page2 = Page2(root)
root.mainloop()

Now in the next example see how I turn the stringvars into variables
of Page1 and Page2 instances (not classes), making them local instead
of global. Then I am forced to pass a reference for the widget page1
object into the widget page2 object.

This looks more close to what you are asking.

About MRO trouble, if you avoid multiple inheritance
it won't happen.

Or you deal with it usually by using super()

In your case the error is because you store the widget in
the object/instance (in self.somename), and then you try
to invoke a widget method qualifying with the class name.
There is no widget there in the class for you to use a method.

So the search using the method resolution order fails,
because there is no corresponding name there.

Note that I have not used multiple inheritance, so I could
have just written tk.Frame. instead of calling super. I like
super because it makes clear in the text that I am invoking the parent
class but super is really needed only when there are multiple parents
and various levels of subclassing (usually forming a diamond shape).

Now the example:

import tkinter as tk
from tkinter import ttk

class Page1(tk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)

self.input_file1 = tk.StringVar()

self.label1 = ttk.Label(self, text='Filename:')
self.entry1 = ttk.Entry(self, textvariable=self.input_file1)
self.label1.pack(side=tk.LEFT)
self.entry1.pack()

class Page2(tk.Frame):
# note the page1 reference being
# passed to initializer and stored in a var
# local to this instance:
def __init__(self, parent, page1, *args, **kwargs):
super().__init__(parent, *args, **kwargs)

self.page1 = page1

self.input_file2 = tk.StringVar()

self.label1 = ttk.Label(self, text='Filename:')
self.entry1 = ttk.Entry(self, textvariable=self.input_file2)
self.button1 = ttk.Button(self, text='Copy Here',
command=self.copy_filename)
self.label1.pack(side=tk.LEFT)
self.entry1.pack(side=tk.LEFT)
self.button1.pack()

def copy_filename(self):
# see how the page1 refernce is used to acess
# the Page1 instance
self.input_file2.set(page1.input_file1.get())

root = tk.Tk() # has to exist for the StringVars to be created

page1 = Page1(root)
page2 = Page2(root, page1) # pass a reference to page1 instance
page1.pack(side=tk.LEFT)
page2.pack(side=tk.LEFT)
root.mainloop()

How to access variables from different class in tkinter to use in different functions?

printName and close_window methods will be evaluated at last, unstead of declaring them outside a class move them to Page1 and change buttons commands:

class Page1(Page):
def __init__(self, *args, **kwargs):
...
button_1 = tk.Button(..., command=self.printName)
button_3 = tk.Button(..., command=self.close_window)

def printName(self):
print(self.last_user_name.get())

def close_window(self):
root.destroy()
driver.close()

You have a NameError: name 'OMcreat' is not defined in Page2.

class Page2(Page):
def __init__(self, *args, **kwargs):
...
button_2 = tk.Button(..., command=self.OMcreat)
button_2.place(x=750,y=400)

def OMcreat(self):
pass

The value of last_user_name should be empty until a user fills in it.
replace it with

self.last_user_name = self.shared_data["last_user_name"]

and use get method when needed.

heading variable will be of None type which is the result of pack method (same with place method), so change it to the following and do the same with other variables:

heading= tk.Label(...)
heading.pack()

EDIT
The reference of the MainView instance is already available in args.To access pages instances, you can define them as an instance variable:

self.p1 = Page1(self)

How to pass a data between different classes defined for tabs in my tkinter GUI?

You never create an instance of Data. Instead, you're initializing self.data to an empty dictionary. You need to create an instance of Data and pass that to the tabs.

class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
...
self.app_data = Data()
...

How to properly access a StringVar() of a class from another class - Python - tkinter

I had to change a few things but for the most part the major solution is to move your StringVar()'s into the main class. Then next we can use the controller argument in the other 2 classes to manipulate the data.

I added a function on page 2 to deal with updating the the label StringVar.
Because of this I deleted the other function you had for this.

I had to change your entry field to a class attribute so we can use its content in the new method. I also had to create a class attribute for the controller in page 2 so we can use the controller in the method as well.

Now there might be an easier way but this is what I managed with your code.

import tkinter as tk

class TestMain(tk.Tk):

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

self.title('TEST GUI')
# Moved StringVar()'s to the main class
self.page1_label = tk.StringVar()
self.page2_entry = tk.StringVar()
container = tk.Frame(self)
container.pack(side='top')
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (PageOne, PageTwo):
frame = F(container, self)
self.frames[F] = frame
frame.configure(background='lightgrey')
frame.grid(row=0, column=0, sticky='nswe')
self.show_frame(PageOne)

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

# Deleted this function

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

frame_eb_data = tk.Frame(self, width=100, height=100, bg="orange")
frame_eb_data.grid(row=0, column=0, sticky='nsew', padx=5, pady=5)
frame_but_right = tk.Frame(self, width=240, height=60, bg="yellow")
frame_but_right.grid(row=1, column=0, padx=5, pady=5, sticky='nsew')
lab_eb_data = tk.Label(frame_eb_data, background='#DDD4EF', textvariable=controller.page1_label)
lab_eb_data.grid(row=0, column=0)

b_ebdata = tk.Button(frame_but_right, text="Page 2", width=10, height=2, command=lambda: controller.show_frame(PageTwo))
b_ebdata.grid(row=0, column=0)

class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# Added the self.controller so the method below can use it.
self.controller = controller
frame_buttons = tk.Frame(self, width=455, bg="#DDD4EF", colormap="new")
frame_buttons.grid(row=0, column=0, padx=5, pady=5, sticky='e')
frame_up_left = tk.Frame(self, width=485, height=260, bg="#89E3FA", colormap="new")
frame_up_left.grid(row=1, column=0, sticky='w', padx=5, pady=5)

b_data = tk.Label(frame_buttons, text='Example GUI', font='TrebuchetMS 30 bold', background="#DDD4EF")
b_data.grid(row=0, column=0, padx=13, pady=5, sticky='w')
b5 = tk.Button(frame_buttons, text='Set Text', command= self.update_p2_label)
b5.grid(row=0, column=2, padx=5, pady=5, sticky='e')
b6 = tk.Button(frame_buttons, text='Page 1', command=lambda: controller.show_frame(PageOne))
b6.grid(row=0, column=3, padx=5, pady=5, sticky='e')

self.entry_nombre_fld = tk.Entry(frame_up_left, width=40)
self.entry_nombre_fld.grid(row=1, column=1, columnspan=3, sticky='w')
label_2 = tk.Label(frame_up_left, text="Name:", font=("bold", 14))
label_2.grid(row=1, column=0, sticky='e')
# Added this function to update the page1_label StringVar.
def update_p2_label(self):
self.controller.page1_label.set(self.entry_nombre_fld.get())

app = TestMain()
app.mainloop()

How do I pass variables in different classes?

First, nextpage should definitely not inherit from SampleApp. So, change the definition to:

class nextpage(tk.Frame):

Second, margaritapizza is an attribute of SampleApp, so you need to refer to it via the instance of SampleApp. In your case, self.controller is that instance, so you can use self.controller.margaritapizza.

There are other problems with how you define the entry, I'll fix them as well:

self.entry = tk.Entry(self, borderwidth=2,width=15,textvariable=self.controller.margaritapizza)
self.entry.pack()


Related Topics



Leave a reply



Submit