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:
Here's what it looks like when artwork has a "dark orange" colored background:
Here's what it looks like when artwork has a "yellow" colored background which forces the text to be black:
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()
andsave()
functions inner functions ofnew_win()
, which allows them to access the local variables innew_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
How to Install Pip for Python 3 on MAC Os X
Validating Detailed Types in Python Dataclasses
Send Data from a Textbox into Flask
Importerror When Importing Tkinter in Python
Constructing a Co-Occurrence Matrix in Python Pandas
Python and Openssl Version Reference Issue on Os X
How to Import a Text File on Aws S3 into Pandas Without Writing to Disk
How to Convert an Rgb Image to Numpy Array
Python Urllib2 with Keep Alive
Why Use Os.Path.Join Over String Concatenation
How to Get Rid of Beautifulsoup User Warning
How to Check That Multiple Keys Are in a Dict in a Single Pass
Replace Nth Occurrence of Substring in String
Access to Table Objects on Webpage Using Python Selenium
How to Break Up This Long Line in Python
Get an Attribute Value Based on the Name Attribute with Beautifulsoup