tkinter: How to access a StringVar and objects from a different class? - python

I'm having a simple tkinter two frame application with a Label, Entry and Button widget and I want to access a StringVar() of FrameOne with a Entry and Button of FrameTwo.
If have seen a lots of examples of code, but do not get how this is been done in my example below. Many programmers are using a controller. If I would use a controller, I end up from an error to another.
For example:
FirstFrame = FrameOne(mainWindow)`
TypeError: __init__() missing 1 required positional argument: 'controller'
Which I completely understand, because I do not pass anything into the new 'controller' class argument when calling the Frame class. But I do not know what I should pass into this to solve it. Perhaps it is also caused by the lack of knowledge of using class variables (any literature tips are welcome).
The same counts for the solution to inherit FrameOne into FrameTwo. I bump into the same amount of errors applying to my code.
Another thing is that many programmers have examples of two frames that are not visible at the same time, while in my example I have two frames underneath each other at the same time.
An different related issue that I have is, what if the label widget of FrameOne was a Text widget? How do I access the widget from FrameTwo.
I could make it work with globals, but I do not want to use such writing and I will keep the access widget problem anyhow.
Please find my code below:
import tkinter as tk
class AppWindow():
def __init__(self, master):
self.master = master
master.title("Test Application")
master.geometry("1060x680")
master.grid_propagate(False)
class FrameOne(tk.Frame):
def __init__(self, parent):
super().__init__()
self["borderwidth"]=5
self["relief"]="ridge"
self.LabelText = tk.StringVar()
self.LabelText.set("It is not working yet")
self.testlabel = tk.Label(self, textvariable=self.LabelText)
self.testlabel.grid(row=1, column=1)
class FrameTwo(tk.Frame):
def __init__(self, parent):
super().__init__()
self["borderwidth"]=5
self["relief"]="ridge"
self.testentry = tk.Entry(self)
self.testentry.insert("end", "This should be working")
self.testentry.grid(row=1,column=1)
self.testbutton = tk.Button(self, text="Test the label", command=self.updatelabel)
self.testbutton.grid(row=1,column=2)
def updatelabel(self):
FrameOne.LabelText.set(self.testentry.get()) #HOW TO FIX THIS CODE THE RIGHT WAY?
#Create a window as defined in the AppWindow class
mainWindow = AppWindow(tk.Tk())
#Create a Frame as defined in class FrameOne
FirstFrame = FrameOne(mainWindow)
FirstFrame.grid(row=0, column=0) #Positioning Frame on Window
#Create a Frame as defined in class FrameOne
SecondFrame = FrameTwo(mainWindow)
SecondFrame.grid(row=1, column=0) #Positioning Frame on Window

Like with any python object, you access an attribute of an object using a reference to the object.
In your case, updatelabel should look like this:
def updatelabel(self):
FirstFrame.LabelText.set(self.testentry.get())
Note: your use of uppercase characters for instance variables makes your code much harder to comprehend. I recommend following the naming guidelines in PEP8.

Related

How to change the attributes of a Tkinter Frame subclass?

Context
I am developing an app with Tkinter in Python 3.8 for some school students, that gets them to input some working out to a maths problem, then checks whether they are correct.
Approach
Each widget (or groups of widgets) are organised onto a ttk.Frame subclass, which is then instantiated in a MainWindow class that organises them using the .grid() geometry manager. For example:
class Questions(ttk.Frame):
def __init__(self, master):
super().__init__(master)
question_style = ttk.Style(self)
question_style.configure("q.TLabel", padding=30)
question = ttk.Label(self, text=f"{QA[q_number][0]}",
justify=tk.CENTER, style="q.TLabel")
question.pack(fill=tk.BOTH)
The above Questions class is responsible for displaying the questions. I have taken the same approach for pretty much every widget - navigation buttons, entry fields, etc. I took this approach because I thought it would afford me the greatest design flexibilities, as not only could I change the appearance of each widget, but also the frame on which they were placed (from my experience, school students can be very aesthetically picky).
Problem
I cannot figure out how to change the attributes of the Frame on which the widgets are placed. For instance, how would I change the height and width of the frame? I have tried the following, to no avail:
class Questions(ttk.Frame):
def __init__(self, master):
super().__init__(master)
self.height = 50
self.width = 50
question_style = ttk.Style(self)
question_style.configure("q.TLabel", padding=30)
question = ttk.Label(self, text=f"{QA[q_number][0]}",
justify=tk.CENTER, style="q.TLabel")
question.pack(fill=tk.BOTH)
I have also tried self.configure(height=50) but that didn't work either.
How can I change the frame on which these widgets are placed?
Is there a better way to tackle this? Should I abandon the Frame subclass approach?
Any help is greatly appreciated. Thanks.
p.s. I wasn't quite sure how to word this question so any advice on that is also appreciated.
I do not know Tkinter much, but have you tried by passing the parameters to the constructor of the superclass?
super().__init__(self, width=50, height=50)

Attempting to nest several frames within a single frame with Tkinter. How do I accomplish this in an object oriented fashion?

My code basically does this:
Which is clearly not what I want to try. For further clarification I would like my window to look similar to this:
from tkinter import *
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
class Encoding(tk.Tk):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.mode = StringVar()
## If I remove the next line it breaks it entirely.
self.encoding_frame = ttk.Frame(parent)
self.encrypt = ttk.Radiobutton(self.encoding_frame, text='Encrypt', variable=self.mode, value='encrypt')
self.decrypt = ttk.Radiobutton(self.encoding_frame, text='Decrypt', variable=self.mode, value='decrypt')
self.encrypt.grid(column=0, row=0, ipadx=2, sticky=W)
self.decrypt.grid(column=0, row=1, ipadx=2, sticky=W)
self.encoding_frame.grid(column=0, columnspan=3, row=2, sticky=S)
class MainApplication(tk.Frame, Encoding):
# Create a main frame here.
# Would like frames to be nested within this frame. This seems redundant but nesting with a main
# frame allows for consistent themes, and gives additional control of layout between subframes.
# Inheritance is confusing.
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.main_frame = ttk.LabelFrame(parent, text="Main Window", width=500, height=500)
self.main_frame['borderwidth'] = 3
self.main_frame['relief'] = 'raised'
self.main_frame.grid(column=0, row=0)
self.encoding = Encoding(self)
## I wrote the following line hoping that I could have main_frame become the parent frame.
self.encoding.encoding_frame = ttk.LabelFrame(self.main_frame)
if __name__ == "__main__":
app = MainApplication(root)
root.mainloop()
I am clearly not getting something right. The whole reason I rewrote the program is so that I could gain a greater understanding/confidence with object oriented code. I am hoping that I can get better insight with this, so any help would be amazing.
There are are several problems going on with your code.
Perhaps the biggest problem is that Encoding inherits from tk.Tk, MainApplication inherits from tk.Frame and Encoding (making it both a root window and a frame), and then MainApplication creates an instance of Encoding. Plus, you explicitly create another instance of tk.Tk(), giving you two root windows. That all needs to be untangled.
Inheritance create a "is a" relationship. By having MainApplication inherit from Encoding you are saying that MainApplication is a Encoding object. That is not the case in your code - an Encoding object represents only a small part of the application. For that you want composition, not inheritance, ie: MainApplication has a Encoding object.
So, the first step is to remove Encoding from the list of classes that MainApplication inherits from.
Another thing that can probably be removed is self.encoding_frame. I see no reason to have it since MainApplication itself is a frame. Instead, have MainApplication inherit from ttk.LabelFrame rather than tk.Frame.
The final thing is that since MainApplication creates Encoding, it should be responsible for calling grid or pack on the instance of Encoding.
Altogether, MainApplication can be pared down to this:
class MainApplication(ttk.LabelFrame):
def __init__(self, parent, *args, **kwargs):
ttk.LabelFrame.__init__(self, parent, *args, **kwargs)
self.configure(text="Main Window")
self['borderwidth'] = 3
self['relief'] = 'raised'
self.encoding = Encoding(self)
self.encoding.grid(row=0, column=0, sticky="ew")
That's not 100% complete, but it's a good place to start. Based on your image I'm guessing you'll have other classes for other parts of the main application -- the message widget, the key widgets, and the transcription window.
For Encoding, much of the same advice applies. Since it's only part of the application, it shouldn't inherit from tk.Tk. Instead, you can inherit from ttk.Frame and then remove self.encoding_frame since the Encoding object itself is already a frame.
With those changes, Encoding should look something like the following. Notice how the radiobuttons have self as their parent. If you're creating proper objects, all widgets inside the class need to be a child of the class itself or one of its descendants. A class like this should never put anything in parent except itself.
class Encoding(ttk.Frame):
def __init__(self, parent, *args, **kwargs):
ttk.Frame.__init__(self, parent, *args, **kwargs)
self.mode = StringVar()
self.encrypt = ttk.Radiobutton(self, text='Encrypt', variable=self.mode, value='encrypt')
self.decrypt = ttk.Radiobutton(self, text='Decrypt', variable=self.mode, value='decrypt')
self.encrypt.grid(column=0, row=0, ipadx=2, sticky=W)
self.decrypt.grid(column=0, row=1, ipadx=2, sticky=W)
Finally, since MainApplication is now a frame -- instead of inheriting from Encoding which inherits from tk.Tk -- the block of code that creates an instance of MainApplication needs to be responsible for calling pack or grid. Since MainApplication is the only widget directly inside of the root window, pack is the best choice since you don't have to remember to configure row and column weights to get proper behavior when the window is resized.
Also, I recommend creating root in the same block rather than at the very start of the program.
Your bottom block of code should look like this:
if __name__ == "__main__":
root = tk.Tk()
app = MainApplication(root)
app.pack(fill="both", expand=True)
root.mainloop()

Obtaining widget values in tkinter

How do I refer to the widget values in the following code.Here I have added widgets by calling methods in the app class for different frames.Next,I want to access the values in all the Widgets(which the user enters) of all the frames at the same time.But I am not able to figure out how should I refer to them and access their values!
class myapp():
def __init__(self,parent):
self.parent=parent
self.container=Frame(self.parent)
self.container.pack()
self.tab1=Button(self.container,text='tab1',command=self.tab1Click)
self.tab2=Button(self.container,text='tab*emphasized text*2',command=self.tab2Click)
self.tab1.pack()
self.tab2.pack()
def tab1Click(self):
top=Toplevel()
self.container1=Frame(top)
self.add_widget1(self.container1)#self.add_widgeti(parent) is a method in myapp() class to add a widget to a frame
self.add_widget2(self.container1)
self.add_widget3(self.container1)
self.container1.pack()
def tab2Click(self):
top=Toplevel()
self.container2=Frame(top)
self.add_widget2(self.container2)
self.add_widget4(self.container2)
self.add_widget5(self.container2)
self.container2.pack()
def write(self):
#here I want to write the values contained in the widgets in both frames in a file,but I am not able to figure out how do I refer to them and access their values.
Any help will be highly appreciated.Thanks in advance.
The widgets in which the user can write have a get method that returns their content. But in order to do this, you need to store your widget in a class variable for example.
Edit: I had misunderstood the problem and I hadn't realized that the add_widget function would be called for different containers for the same instance. One way to keep track of all created widgets is to create a widget list:
add self.widgets = [] in __init__
define theadd_widget method like that:
def add_widget(self, container):
self.widgets.append(Entry(container, text="enter text here"))
self.widgets[-1].pack()
Then to get the text entered by the user in all widgets (inside the write function):
texts = []
for widget in self.widgets:
texts.append(widget.get())

Python, using tkinter how to customize where classes of ui components are displayed?

I am very new to python, and am currently trying to organize my tkinter app in a slightly different way. I'm trying to use classes to make the app more modular and be able to use methods in the class in multiple places in the app. Here is the updated code that I have:
from tkinter import *
class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.createWidgets()
self.QUIT.pack(side=LEFT)
self.hi_there.pack(side=RIGHT)
Frame.__init__(self, master)
self.pack()
self.createAnotherWidget()
self.title_label.pack(side=LEFT)
self.title_entry.pack(side=RIGHT)
def say_hi(self):
print("hi there, everyone!")
def createWidgets(self):
self.QUIT = Button(self)
self.QUIT["text"] = "QUIT"
self.QUIT["fg"] = "red"
self.QUIT["command"] = self.quit
self.hi_there = Button(self)
self.hi_there["text"] = "Hello",
self.hi_there["command"] = self.say_hi
def createAnotherWidget(self):
self.title_label = Label(self)
self.title_label["text"] = "Title: "
self.title_entry = Entry(self)
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.createWidgets()
self.createAnotherWidget()
root = Tk()
app = Application(master=root)
app.mainloop()
root.destroy()
This runs without errors, but nothing shows in the window. How can I customize where the code for these methods will be placed when rendered? For this example, I'm simply wanting the createAnotherWidget to display below the two buttons in createWidgets
One solution is to have the caller be responsible for calling pack or grid on the individual widgets. This requires that you save references to the widgets somehow, such as attributes of the object. You do this already for some widgets, but not for others. You need to be consistent.
Do this by moving the calls to pack() from the functions and into your __init__:
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.createWidgets()
self.createAnotherWidget()
self.QUIT.pack(...)
self.hi_there.pack(...)
self.title_label.pack(...)
self.title_entry.pack(...)
Of course, you'll need to modify createAnotherWidget to save references to the widgets.
The point being, creating widgets and laying out widgets on the screen are two separate problems that should be solved separately.
That being said, it's a bit unusual to have functions that create multiple widgets that are designed to be stitched together by some other function.
A more common pattern is for functions to create widgets that are related, and to manage the layout of the related widgets itself. That way the caller only has to worry about organizing the groups of widgets rather than a bunch of individual widgets.
For example, you might have one function that creates a toolbar. Another that creates the main area with scrollbars. Another function would create a footer. Another that creates a form with a bunch of label and entry widgets. Another one that creates a label/entry combination. And so on.

In Python, how to make "class" (when passed as a parameter) work the same as "class.attribute"?

So, I am currently making a custom Tkinter module/API mainly just so I can get a better understanding of Tkinter, but I've run into a problem. Suppose I have the following code:
import Tkinter as tk
Class Window(object): # Equivalent of Tkinters Tk() in my module
def __init__(self):
self.root = tk.Tk()
...
Class Label(object): # Equivalent of Tkinters label widget in my module
def __init__(self, master):
self.label = tk.Label(master, text="Hello world!")
...
How can I make it so when I'm creating a label widget, I may do
master = Window()
label = Label(master)
Instead of having to do
master = Window()
label = Label(master.root)
I know I can inherit tk.Tk into my window class, but I'm just curious if there's a different way of doing it. I've done some research and it seems it may have something to do with one of the built-in methods with the leading and trailing double underscores (not sure what they're called), but I don't know what most of those do.
P.S. Sorry for bad title, wasn't sure how to describe it in a short sentence :P
You can write:
self.label = tk.Label(master.root, text="Hello world!")
But as Brian and Paul said, inherit from tk.Tk is the best thing to do.

Categories

Resources