Getting Values from Object Oriented Tkinter

Getting values from object oriented tkinter

Ok this is a very common mistake. When you create your tk.Entry you do this:

self.email = tk.Entry(self).grid(column=1, row=4)

That creates the entry then immediately calls its .pack method. It stores whatever .pack returns (always None) to self.email. What you want to do is this:

self.email = tk.Entry(self)
self.email.grid(column=1, row=4)

Tkinter get names, attributes and values of object by reference number

To address the issue of configuring. In this application there is a frame with 8 tkinter buttons that do not have names. Every few minutes the background color for artwork changes and it needs to be propagated down to all the buttons.

Here is the function to do that:

def config_all_buttons(level, **kwargs):
''' Configure all tk buttons within a frame (doesn't work for toplevel?).

level = frame name, eg self.play_btn

**kwargs = tkinter_button.configure(keywords and values). For example:
fg="#000000", bg="#ffffff", padx=5
'''
for k, v in level.children.items():

if isinstance(v, tk.Button):
if v["image"] == "":
# We can't configure image labels that have a value
v.configure(**kwargs)

config_all_buttons(v, **kwargs)

Here is how you call the function from your mainline or class:

        self.play_frm_bg = self.play_resized_art.getpixel((3,3))
hex_background = img.rgb_to_hex(self.play_frm_bg)
self.play_frm_fg = img.contrasting_rgb_color(self.play_frm_bg)
hex_foreground = img.rgb_to_hex(self.play_frm_fg)
self.play_frm.configure(bg=hex_background)
toolkit.config_all_labels(self.play_frm, fg=hex_foreground, \
bg=hex_background)
toolkit.config_all_buttons(self.play_btn, fg=hex_foreground, \
bg=hex_background)

Here's what it looks like when artwork has a "dark chocolate" colored background:

mserve confg_all_labels.gif

Here's what it looks like when artwork has a "dark orange" colored background:

mserve confg_all_labels2.gif

Here's what it looks like when artwork has a "yellow" colored background which forces the text to be black:

mserve config_all_labels3.gif

Passing data between windows in Tkinter using object-oriented approach

The problem is that self.master doesn't refer to the instance of MainApp, it refers to the root window. This is because you don't pass in the instance of MainApp to NewWindow, and self.master refers to a variable defined by tkinter.

You need to pass the instance of MainApp to NewWindow, save it, and then refer to it when trying to access testVar.

One way would be to do something like this (though personally I think a function would be better than lambda here):

ttk.Button(self.master, text = "Open Window", command=lambda: NewWindow(self, self.master)).pack()

Next, you have to define NewWindow to accept and save the parameter, and use it instead of self.master

class NewWindow(Toplevel):
def __init__(self, mainapp, master = None):
super().__init__(master = master)
self.mainapp = mainapp
...
def SaveData(self):
messagebox.showinfo(title=None, message="Save complete: " + self.mainapp.testVar)

tkinter how to change attribute value of object using OOP?

Tkinter is based on different progamming language, named tcl and thats why things seem a bit unpythonic here.

The tk.Frame object isnt a pure python object, rather its an wrapped object in a tcl interpreter and thats why you cant address the attributes you intuitivly think you can. You need to adress the attributes in a way the tcl interpreter is abel to handle and therefor methods are created like widget.configure.

To achive what you want with your current code, it would look like:

import tkinter as tk

root = tk.Tk()

class MyFrame():
def __init__(self, bg_color):
self.window = tk.Frame(width=500,height=500,bg=bg_color)

frame_obj = MyFrame('blue')
frame_obj.window.pack(fill=tk.BOTH)
frame_obj.window.configure(bg='yellow')

root.mainloop()

In addition, the proper way for an OOP approach for a frame would look like this:

class MyFrame(tk.Frame):
def __init__(self,master,**kwargs):
super().__init__(master)
self.configure(**kwargs)

frame_obj = MyFrame(root,width=500,height=500,bg='green')
frame_obj.pack(fill=tk.BOTH)

This way your class becomes a child of the tk.Frame object and you can adress it directly. The syntax self in this context refers directly to the tk.Frame object. Also it is good practice to use the format of

def __init__(self,master,**kwargs):

while its the same for the parent/tk.Frame. It has a single positional argument, named master and keywords arguments for the configuration of the Frame.

Please take a look at a tutorial for tkinter and for OOP. If you had you would know that. Please dont feel offended, but StackOverflow requiers a brief reasearch and that includes to read documentation and take tutorials.

Python : how can I get the value of an Entry in a function

The main issue you have here is scope, since you're trying to access the Entry and StringVar from functions that don't have access to it.

The name ent1 defined inside new_win() will not be accessible from return_nom().

Furthermore, it seems to me that what you actually want to query is the StringVar and not the Entry widget itself.

How you can pass the StringVar to the called functions, there are several ways:

  • Make the strvar a global variable, that way the other functions will have access to it. This is probably the worst possible solution to this problem, since the point of having functions is to avoid namespace pollution of a global namespace.
  • Make the return_nom() and save() functions inner functions of new_win(), which allows them to access the local variables in new_win() as a closure. This is slightly better.
  • Use an object-oriented interface, where your StringVar is an instance member and your save() is a method. State such as the StringVar is available to the whole instance. This is probably the best here, it's how Tkinter is actually intended to be used, it most naturally fits an object-oriented approach.

An example of using inner functions would be:

def new_win():
top = Toplevel(fen)
top.title("new window")
strvar = StringVar()
def return_nom(*args):
return strvar.get()
def save():
print("your name is", return_nom())
ent1 = Entry(top, textvariable=strvar)
bouton1 = Button(top, text='print', command=save)
bouton1.pack()
ent1.pack()
top.mainloop()

An example of an object-oriented approach would be:

class MyDialog:
def __init__(self, fen):
self.fen = fen
self.strvar = StringVar()

def return_nom(self, *args):
return self.strvar.get()

def save():
print("your name is", self.return_nom())

def new_win(self):
top = Toplevel(self.fen)
top.title("new window")
ent1 = Entry(top, textvariable=self.strvar)
bouton1 = Button(top, text='print', command=self.save)
bouton1.pack()
ent1.pack()
top.mainloop()

And then at the top-level:

my_dialog = MyDialog(fen)
bouton = Button(fen, text='new window', command=my_dialog.new_win)

But the above is still not the best approach, which would actually be to create subclasses of Tkinter.Frame for your windows, connect them together through methods and have your top-level code only instantiate a main Application class and call app.mainloop() on it, letting it drive the whole application flow through events connected to methods and other frames.

See a simple Hello World program in the Python tkinter documentation on the Python standard library to get a somewhat better idea of how tkinter is intended to be used.

That documentation also has pointers to many other resources on tkinter and on Tk itself that you can follow to get more in-depth knowledge of that toolkit library.

Python and Tkinter: object oriented programming query

Once Tk.mainloop is entered, no further code will be executed. Instead, the Tk event loop will take over (hence the name).

What that means is that if you, eg, did something like this:

def main():
...
main_window.mainloop()
print 'Hello world!'

then that print statement would never be executed (or, at least, not while the GUI is running).


So, with that in mind, why is there a problem with creating the root window and executing main loop within the constructor (the __init__ statement)? Well, two basic reasons:

  • It would mean that the constructor never returns, which is unexpected. If a programmer sees this:

    def main():
    Nexus()
    print 'Hello world!'

    then he or she will expect that print statement to be executed. As a rule, you don't expect creating an instance of a class to be the kind of thing which will cause an infinite loop (as the event loop is).

  • Related to that is the second reason: it would not be possible to create more than one instance of Nexus, because as soon as you create one, Tk.mainloop will take over. Again, that's unexpected: a class is a description of a type of object, and you would normally expect to be able to instantiate more than one object like that.

    At the moment, if you write:

    def main():
    ...
    Nexus(main_window)
    Nexus(main_window)

    then you'll get two copies of your Nexus window on the screen. That's expected, and sensible. The alternative would not be.


So what's the take-away message?

When you're dealing with GUI programs, entering the event loop is the last thing you want to do. Your setup might involve creating one object (as now), or it might involve creating many objects (eg, a complex GUI app might have two or three windows).

Because we want to be able to write similar code in both cases, the usual approach is to create the root window (the Tk object) once, and then pass it in as a reference to any classes that need to know about it.

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.



Related Topics



Leave a reply



Submit