Reference my tkinter label object using a function outside of the class - python

I want to change the configurations on one of my labels, but cannot figure out how to reference it in a function outside of the class. My goal is to crate a function that will be placed in a loop to check the value of "my_turn". Depending on the value of "my_turn", I want to change the configuration of "self.turn" label in the "TeamFrame" class, however I can't figure out how to reference it outside of the class.
class LoginScreen(tk.Canvas):
def __init__(self, parent):
tk.Canvas.__init__(self, parent)
self.parent = parent
self.create_image(0, 0, image=my_img, anchor="nw")
self.button = tk.Button(self.frame, text="Connect", bg='burlywood3', activebackground='burlywood3', font="12",
command=lambda: login())
self.button.grid(row=4, columnspan=3, pady=10)
def login():
my_name = self.entry.get()
msg = str(my_name)
client.send(msg.encode(FORMAT))
self.destroy()
root.title(f"Main Application - {my_name}")
draft = MainApplication(root, my_name)
draft.pack(side="top", fill="both", expand=True)
threading.Thread(target=start_app).start()
class MainApplication(tk.Canvas):
def __init__(self, parent, name):
tk.Canvas.__init__(self, parent)
self.parent = parent
self.create_image(0, 0, image=my_img, anchor="nw")
self.team = TeamFrame(self, 1, 4, name)
class TeamFrame(tk.Frame):
def __init__(self, parent, r, c, name):
tk.Frame.__init__(self, parent)
self.parent = parent
self.configure(bd=1, relief="ridge", bg='wheat3')
self.grid(row=r, rowspan=2, column=c, padx=10, pady=15, sticky=tk.NE)
self.turn = tk.Label(self, bg='wheat3', text=f"You are not on the clock.\n ",
fg='gray28', font='bold 12', disabledforeground='red3')
self.turn.pack(pady=10)
def start_app():
# threaded loop that check "my_turn" and update lists and execute additional game functions
if __name__ == "__main__":
plr.sort_players(sort_method)
root = tk.Tk()
root.minsize(400, 300)
root.title(f"Login")
root.iconbitmap('football.ico')
my_img = ImageTk.PhotoImage(file='field.gif')
LoginScreen(root).pack(side="top", fill="both", expand=True)
root.mainloop()

You can pass the instance of MainApplication, draft, to start_app():
threading.Thread(target=start_app, args=(draft,)).start()
Then you can access the required label via main_app.team.turn (main_app is the name of the new argument of start_app()) as below:
def start_app(main_app):
# do something
main_app.team.turn['text'] = 'Hello'
# do other stuff

Related

Tkinter Python- function which can access data from other tabs

I would like to create a function in Main_Application which would access data from other tabs... how can i achieve it?
I was trying to access it by typing "Page_2.entry2.insert(0, "test")" but it returns Attribute error
I would appreciate any kind of help as i am struggling with it for some time already
class Main_Application(tk.Tk):
def __init__(self):
super().__init__()
self.title("HAT")
first_label = ttk.Label(self, text="lol").pack()
#Notebook
nb = ttk.Notebook(self)
nb.pack()
#Frames
p1 = tk.Frame(nb)
p2 = tk.Frame(nb)
p3 = tk.Frame(nb)
p4 = tk.Frame(nb)
nb.add(p1, text="Page1")
nb.add(p2, text="Page2")
nb.add(p3, text="Page3")
nb.add(p4, text="Page4")
Page_1(p1).pack()
Page_2(p2).pack()
def load_it_all(self):
print(Page_01(self).user_input.get())
Page_02.entry2.insert(0, "test")
# <HERE I WOULD LIKE TO CALL THE DATA FROM
#First one prints the data from Page_01 and second i would like to insert data in Page_02.entry2
class Page_1(ttk.Frame):
def __init__(self, container):
super().__init__(container)
#Stringvar
self.user_input = tk.StringVar()
#Labels
label = ttk.Label(self, text="HU3: ")
#Entries
entry = ttk.Entry(self, textvariable=self.user_input)
#Inserts
entry.insert(0, "4500")
#Buttons
button = ttk.Button(self, text="Apply", command=lambda: Main_Application.load_it_all(self))
#Geo
label.pack()
entry.pack()
button.pack()
def test(self):
print(self.user_input.get())
class Page_2(ttk.Frame):
def __init__(self, container):
super().__init__(container)
self.user_input = tk.StringVar()
label = ttk.Label(self, text="Trend from last cycle ")
#Entries
entry2 = ttk.Entry(self, textvariable=self.user_input)
#Inserts
# self.entry2.insert(0, "4500")
#Buttons
# button = ttk.Button(self, text="Apply", command=self.test2)
#Geo
label.pack()
entry2.pack()
# button.pack()
Your issues included
improperly initiating super
too many containers
randomly using ttk widgets
poor understanding of scope
naming syntax errors
unnecessary vars
Below is a working example of what you are trying to do
import tkinter as tk, tkinter.ttk as ttk
class Main(tk.Tk):
def __init__(self):
#this is how to properly init a super
tk.Tk.__init__(self)
#make notebook fill display
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
#Notebook
nb = ttk.Notebook(self)
nb.grid(row=0, column=0, sticky='nswe')
#keep a reference to the pages
self.p1 = Page_1(self)
self.p2 = Page_2(self)
#tabs
nb.add(self.p1, text="Page 1")
nb.add(self.p2, text="Page 2")
def load_it_all(self):
#use the page references to access and share data
self.p2.input = self.p1.input
class Page_1(tk.Frame):
#property
def input(self):
return self.userinput.get()
#input.setter
def input(self, value):
self.userinput.delete(0, 'end')
self.userinput.insert(0, value)
def __init__(self, master, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
tk.Label(self, text="HU3: ").grid(row=0, column=0)
self.userinput = tk.Entry(self)
self.userinput.grid(row=0, column=1)
self.input="4500"
tk.Button(self, text="Apply", command=master.load_it_all).grid(row=0, column=2)
class Page_2(tk.Frame):
#property
def input(self):
return self.userinput.get()
#input.setter
def input(self, value):
self.userinput.delete(0, 'end')
self.userinput.insert(0, value)
def __init__(self, master, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
tk.Label(self, text="Trend from last cycle ").grid(row=0, column=0)
self.userinput = tk.Entry(self)
self.userinput.grid(row=0, column=1)
if __name__ == "__main__":
root = Main()
root.geometry('800x600')
root.title("HAT")
root.mainloop()

How do i configure with a command from another frame after instancing in Tkinter?

I'm trying to make it so whenever i select a radiobutton it will .insert() a value inside of an empty entry that is declared in another class and instanced in the application class.
import tkinter as tk
class DataDisplay(tk.Frame):
""" Provides the GUI, powered by the Tkinter module. """
def __init__(self, parent):
super().__init__()
tk.Frame.__init__(self, parent)
self.parent = parent
self.grid()
self.create_widgets(parent)
def create_widgets(self, parent):
print("Creating widgets... ")
# Radio buttons frame
radiobuttons = Radiobuttons(parent)
radiobuttons.grid(column=1, row=0, sticky=tk.NE)
# ID, calibration date, arrival date frame.
data_entry_frame = tk.Frame(parent, borderwidth=3, relief='ridge')
data_entry_frame.grid(column=0, row=0, sticky=tk.NE)
# Producer info frame
product_info = ProductInfo(data_entry_frame)
product_info.pack(side=tk.TOP)
class Radiobuttons(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
# Setting border-width and border-type
self.configure(borderwidth=3, relief='ridge')
# Setting self.radio to be an integer for function use.
self.radio = tk.IntVar()
# Defining radio-buttons
rb = tk.Radiobutton(self, text='Molybdenum-99', variable=self.radio, value=1, indicatoron=0,
width=15, overrelief='sunken',
command= lambda: DataDisplay.product_info.iso_half_life_value_label.insert(0, 'test'))
rb.pack(anchor=tk.W)
rb = tk.Radiobutton(self, text='Technetium-99M', variable=self.radio, value=2, indicatoron=0,
width=15, overrelief='sunken',
command=lambda: print('Radiobutton selected Technetium99M'))
rb.pack(anchor=tk.W)
def get(self):
return self.radio.get()
class ProductInfo(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.label = tk.Label(self, text='Insert here: ')
self.label.grid(column=0, row=5)
self.iso_half_life_value_label = tk.Entry(self)
self.iso_half_life_value_label.grid(column=1, row=5)
print('Finished creating widgets.')
if __name__ == "__main__":
root = tk.Tk()
app = DataDisplay(root)
root.title("DataDisplay")
root.geometry('800x800')
root.resizable(True, True)
root.mainloop()
The error i'm getting with my current attempt: AttributeError: type object 'DataDisplay' has no attribute 'product_info'
Expected result is to have a custom value based on radiobutton selection inserted into the product_info label.
As is the case with any python object, you need a reference to the object in order to change it. The simplest solution is to pass a reference to your objects to the other objects that need them.
For example, if you pass the instance of DataDisplay to Radiobuttons, Radiobuttons can then access the attributes of DataDisplay. For example:
class DataDisplay(...):
def create_widgets(...):
...
radiobuttons = Radiobuttons(parent, data_display=self)
...
class Radiobuttons(tk.Frame):
def __init__(self, parent, data_display):
...
rb = tk.Radiobutton(..., command= lambda: data_display.product_info.iso_half_life_value_label.insert(0, 'test'))
...
The other part of the problem is that you're not saving product_info as an attribute of the object. You need to do that when you create it:
self.product_info = ProductInfo(...)
Solved by making product_info a global variable. Not sure if this is best practice however.

Calling a widget from another class to change its properties in tKinter

I have made a function in the main constructor of my tKinter app which updates certain properties of widgets e.g. their text across multiple frames. What I'm trying to do is change widgets in multiple frames at the same time while in a controller frame.
def update_widgets(self, frame_list, widget_name, criteria, output):
for i in frame_list:
i.widget_name.config(criteria=output)
# update_widgets(self, [Main, AnalysisSection], text_label, text, "foo")
# would result in Main.text_label_config(text="foo") and
# AnalysisSection.text_label_config(text="foo") ideally.
However with this code, I'm encountering two problems. Firstly, I'm getting an attribute error stating that both frames don't have the attribute widget_name. Secondly, when I tried to refer to the widget names with the self prefix, both frames say they don't have the attribute self. Is there a way to fix this?
Full program below:
import tkinter as tk
class Root(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.frames = {}
container = tk.Frame(self)
container.pack(side="bottom", expand=True)#fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
for X in (A, B):
frame=X(container, self)
self.frames[X]=frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(A)
def show_frame(self, page):
frame = self.frames[page]
frame.tkraise()
def update_widgets(self, frame_list, widget_name, criteria, output):
for i in frame_list:
frame = self.frames[i]
widget = getattr(frame, widget_name)
widget[criteria] = output
class A(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.text = 'hello'
self.classLabel = tk.Label(self, text="Frame A")
self.classLabel.pack(side=tk.TOP)
# trying to change this widget
self.wordLabel = tk.Label(self, text="None")
self.wordLabel.pack(side=tk.TOP)
self.changeTextLabel = tk.Label(self, text="Change text above across both frames").pack(side=tk.TOP)
self.changeTextEntry = tk.Entry(self, bg='pink')
self.changeTextEntry.pack(side=tk.TOP)
self.changeFrameButton = tk.Button(text="Change to Frame B", command=lambda: self.controller.show_frame(B))
self.changeFrameButton.pack(side=tk.TOP, fill=tk.X)
self.changeTextEntryButton = tk.Button(self, text="ENTER", width=5, command=lambda: self.controller.update_widgets([A, B], 'self.wordLabel', 'text', self.changeTextEntry.get()))
self.changeTextEntryButton.pack(side=tk.TOP, fill=tk.X)
### calling this function outside of the button; this is already
### called within a function in my project.
x = self.controller.update_widgets([A, B], 'wordLabel', 'text', '*initial change*')
class B(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.text = 'hello'
self.classLabel = tk.Label(self, text="Frame B")
self.classLabel.pack(side=tk.TOP)
# trying to change this widget
self.wordLabel = tk.Label(self, text="None")
self.wordLabel.pack(side=tk.TOP)
self.changeTextLabel = tk.Label(self, text="Change text above across both frames").pack(side=tk.TOP)
self.changeTextEntry = tk.Entry(self, bg='light yellow').pack(side=tk.TOP)
self.changeFrameButton = tk.Button(text="Change to Frame A", command=lambda: self.controller.show_frame(A))
self.changeFrameButton.pack(side=tk.TOP, fill=tk.X)
self.changeTextEntryButton = tk.Button(self, text="ENTER", width=5, command=lambda: self.controller.update_widgets([A, B], 'self.wordLabel', 'text', self.changeTextEntry.get()))
self.changeTextEntryButton.pack(side=tk.TOP, fill=tk.X)
if __name__ == '__main__':
app = Root()
The problem in your code is that you're trying to get an attribute of a class rather than an instance of a class. You need to convert i to the actual instance of that class. You have the additional problem that you're passing 'self.wordLabel' rather than just 'wordLabel'.
A simple fix is to look up the instance in self.frames
def update_widgets(self, frame_list, widget_name, criteria, output):
for i in frame_list:
frame = self.frames[i]
label = getattr(frame, widget_name)
label[criteria] = output
You also need to change the button command to look like this:
self.changeTextEntryButton = tk.Button(... command=lambda: self.controller.update_widgets([A,B], 'wordLabel', 'text', self.changeTextEntry.get()))
If you intend for update_widgets to always update all of the page classes, there's no reason to pass the list of frame classes in. Instead, you can just iterate over the known classes:
def update_widgets(self, widget_name, criteria, output):
for frame in self.frames.values():
label = getattr(frame, 'classLabel')
label[criteria] = output
You would then need to modify your buttons to remove the list of frame classes:
self.changeTextEntryButton = tk.Button(..., command=lambda: self.controller.update_widgets('wordLabel', 'text', self.changeTextEntry.get()))

Trace radiobutton value defined in child class and pass it to parent class in Python

I am new to Python and Tkinter so unable to figure out which might be the simplest thing to do. Could someone please check the below code and tell me how can I trace value returned by radiobutton defined in child class and pass it to parent class. I get following error after compiling:
AttributeError: Toplevel instance has no attribute 'trace_fun'
I am not sure why am I getting this error since I have defined trace_fun in child class body. I have successfully traced variables in parent class but getting above error while trying to do it in the child class.
from Tkinter import *
class Parent(Frame):
classvar = 0
def __init__(self):
Frame.__init__(self)
self.master.title("Parent WIndow")
self.master.geometry("200x100")
self.grid()
self._button = Button(self, text="Create", width=10, command=self.new_window)
self._button.grid(row=0, column=0, sticky=E+W)
def new_window(self):
self.new = Child()
class Child(Parent, Frame):
def __init__(self):
Parent.__init__(self)
new = Frame.__init__(self)
new = Toplevel(self)
new.title("Child Window")
new.grid()
new._var = IntVar()
new._var.set(0)
new._var.trace("w", new.trace_fun)
new._radioButton = Radiobutton(new, text = "Option 1", variable = new._var, value = 1)
new._radioButton.grid(row=0, column=0, sticky=W, padx=10, pady=10)
new._radioButton2 = Radiobutton(new, text = "Option 2", variable = new._var, value = 2)
new._radioButton2.grid(row=1, column=0, sticky=W, padx=10, pady=10)
new._button = Button(new, text = 'Ok', command=new.destroy)
new._button.grid(row=2, column=0, pady=10)
def trace_fun(new, *args):
print new._var.get()
Parent.classvar = new._var.get()
obj = Parent()
def main():
obj.mainloop()
main()
You've overwritten your new variable here:
new = Frame.__init__(self)
new = Toplevel(self)
After those two statements execute, new is equal to an instance of the Toplevel class.
Next, this code executes:
new._var.trace("w", new.trace_fun)
and in particular:
new.trace_fun
So, you have a Toplevel instance trying to access an attribute named trace_fun. The error message is telling you that the Toplevel class does not have any attribute named trace_fun.
Edit:
You can't call trace_fun on a Toplevel instance--ever. Nor can you call trace_fun on a Parent instance--ever. So print out a copy of your program, then get a pen and circle all the variables that are Toplevel instances; then circle all the variables that are Parent instances. You can't call trace_fun on any of those variables. Alternatively, circle all the variables that are Child instances. You can call trace_fun on those variables.
Here is an example of what you can do:
class Child:
def do_stuff(self): #1) self is an instance of class Child, i.e. the object that is calling this method
self.trace_fun() #2) The Child class defines a method named trace_fun()
#3) Therefore, self can call trace_fun()
x = self.trace_fun #4) ...or you can assign self.trace_fun to a variable
#5) ...or pass self.trace_fun to another function
def trace_fun(self):
print 'hello'
d = Chile()
d.do_stuff()
--output:--
hello
It doesn't look like you have a Parent/Child relationship between your two frames--because the Child frame doesn't use anything inherited from the Parent frame. So you could just create two separate frames for your app. Here is an example:
import Tkinter as tk
class EntryFrame(tk.Frame):
classvar = 0
def __init__(self, root):
tk.Frame.__init__(self, root) #Send root as the parent arg to Frame's __init__ method
root.title("Parent Window")
root.geometry("400x200")
tk.Label(self, text="First").grid(row=0)
tk.Label(self, text="Second").grid(row=1)
e1 = tk.Entry(self)
e2 = tk.Entry(self)
e1.grid(row=0, column=1)
e2.grid(row=1, column=1)
button = tk.Button(self, text="Create", width=10, command=self.create_new_window)
button.grid(row=2, column=0, sticky=tk.E + tk.W)
self.grid()
def create_new_window(self):
RadioButtonFrame()
class RadioButtonFrame(tk.Frame):
def __init__(self):
new_top_level = tk.Toplevel()
tk.Frame.__init__(self, new_top_level) #Send new_top_level as the parent arg to Frame's __init__ method
new_top_level.title("Radio Button Window")
new_top_level.geometry('400x300+0+300') # "width x height + x + y"
self.int_var = int_var = tk.IntVar()
int_var.trace("w", self.trace_func)
int_var.set(0)
rb1 = tk.Radiobutton(self, text = "Option 1", variable = int_var, value = 1)
rb1.grid(row=0, column=0, sticky=tk.W, padx=10, pady=10)
rb2 = tk.Radiobutton(self, text = "Option 2", variable = int_var, value = 2)
rb2.grid(row=1, column=0, sticky=tk.W, padx=10, pady=10)
button = tk.Button(self, text = 'Ok', command=new_top_level.destroy)
button.grid(row=2, column=0, pady=10)
self.grid()
def trace_func(self, *args):
radio_val = self.int_var.get()
print radio_val
EntryFrame.classvar = radio_val
def main():
root = tk.Tk()
my_frame = EntryFrame(root)
root.mainloop()
main()
By making slight changes, now my code is working perfectly. Posting new code for someone stuck on the same point as me earlier. Changes can be seen in the below code:
import Tkinter as tk
class Parent:
classvar = 0
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master)
self.master.title("Parent Window")
self.master.geometry("400x100")
self.frame.grid()
self._button = tk.Button(self.frame, text="Create", width=10, command=self.new_window)
self._button.grid(row=0, column=0, sticky=tk.E+tk.W)
def new_window(self):
self.child_window = tk.Toplevel(self.master)
self.app = Child(self.child_window)
class Child(Parent):
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master)
self.master.title("Child Window")
self.frame.grid()
self._var = IntVar()
self._var.set(0)
self._var.trace("w", self.trace_fun)
self._radioButton = tk.Radiobutton(self.frame, text = "Option 1", variable = self._var, value = 1)
self._radioButton.grid(row=0, column=0, sticky=W, padx=10, pady=10)
self._radioButton2 = tk.Radiobutton(self.frame, text = "Option 2", variable = self._var, value = 2)
self._radioButton2.grid(row=1, column=0, sticky=W, padx=10, pady=10)
self._button = tk.Button(self.frame, text = 'Ok', command=self.master.destroy)
self._button.grid(row=2, column=0, pady=10)
def trace_fun(self, *args):
Parent.classvar = self._var.get()
print Parent.classvar
root = tk.Tk()
obj = Parent(root)
def main():
root.mainloop()
main()

Adding python functionality to Tkinter front end

This is the front end I developed for my application using Tkinter:
from Tkinter import *
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Simple")
self.pack(fill=BOTH, expand=1)
frame = Frame(self, relief="flat", borderwidth=1)
label=Label(frame,text="Scope:")
label.pack(side="left", fill=None, expand=False)
var = StringVar()
var.set("today")
list = OptionMenu(frame, var, "today","yesterday","this week","last week","this month","last month")
list.pack(side="left", fill=None, expand=False)
fetchButton = Button(frame, text="Fetch",command=self.handle(var))
fetchButton.pack(side="left", fill=None, expand=False)
frame.grid(row=1,column=1,pady=4,padx=5,sticky=W)
area = Text(self,height=15,width=60)
area.grid(row=2,column=1,rowspan=1,pady=4,padx=5)
scroll = Scrollbar(self)
scroll.pack(side=RIGHT, fill=Y)
area.config(yscrollcommand=scroll.set)
scroll.config(command=area.yview)
scroll.grid(row=2, column=2, sticky='nsew')
quitButton = Button(self, text="Cancel",command=self.quit)
quitButton.grid(pady=4,padx=5,sticky=W,row=3, column=1)
root = Tk()
app = Example(root)
root.mainloop()
Where exactly do I have to put the handle() method so it can write repeatedly to the text widget? When I put handle() within the Example class and use self.area.insert(), it shows an error saying
Example instance has no attribute 'area'
Please help out.
You need to pass the function object to the Button instance, not a function call. i.e.
fetchButton = Button(frame, text="Fetch",command=self.handle)
To make the handle work in the context of the rest of the code:
from Tkinter import *
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.parent.title("Simple")
self.pack(fill=BOTH, expand=1)
self.init_ui()
def init_ui(self):
self.frame = Frame(self, relief="flat", borderwidth=1)
self.frame.grid(row=1,column=1,pady=4,padx=5,sticky=W)
self.label=Label(self.frame,text="Scope:")
self.label.pack(side="left", fill=None, expand=False)
self.var = StringVar()
self.var.set("today")
self.list = OptionMenu(self.frame, self.var, "today","yesterday",
"this week","last week","this month",
"last month")
self.list.pack(side="left", fill=None, expand=False)
self.fetchButton = Button(self.frame, text="Fetch",command=self.handle)
self.fetchButton.pack(side="left", fill=None, expand=False)
self.area = Text(self,height=15,width=60)
self.area.grid(row=2,column=1,rowspan=1,pady=4,padx=5)
self.scroll = Scrollbar(self)
self.scroll.pack(side=RIGHT, fill=Y)
self.area.config(yscrollcommand=self.scroll.set)
self.scroll.config(command=self.area.yview)
self.scroll.grid(row=2, column=2, sticky='nsew')
self.quitButton = Button(self, text="Cancel",command=self.quit)
self.quitButton.grid(pady=4,padx=5,sticky=W,row=3, column=1)
def handle(self):
self.area.delete(1.0, END)
self.area.insert(CURRENT,self.var.get())
if __name__ == "__main__":
root = Tk()
app = Example(root)
root.mainloop()
Declaring your widgets as attributes will save you a lot of pain an suffering as your application expands. Also keeping references to everything in Tk can stop some unwanted garbage collection, particularly with images in Label instances.
It is also worth noting that using grid and pack interchangeably is likely to lead to bugs later on.

Categories

Resources