I need to access the temperature variable from Monitor Class and print on Graph Class. How can I do that? Please see code below which should compile.
from tkinter import *
import tkinter as tk
import time
class ScientificPumpGui(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.container = tk.Frame(self)
self.container.pack(side="top", fill="both", expand=True)
self.container.grid_rowconfigure(0, weight=1)
self.container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (MonitorPage, GraphPage):
frame = F(self.container)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(MonitorPage)
self.create_buttons()
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def exit_app(self):
exit()
def create_buttons(self):
main_b_height = 2
main_b_width = 20
page_button_pady = 10
self.page_button_main_toolbar = tk.Frame(self, borderwidth=1)
self.page_button_main_toolbar.pack(side=TOP, anchor=CENTER, fill=X)
self.page_button_toolbar = tk.Frame(self.page_button_main_toolbar, borderwidth=1)
self.page_button_toolbar.pack(side=TOP, anchor=CENTER)
self.monitor_page_button = Button(self.page_button_toolbar, text="Monitor Page", width=main_b_width, height=main_b_height, command=lambda: self.show_frame(MonitorPage))
self.monitor_page_button.pack(side=LEFT, anchor=CENTER, pady=page_button_pady)
self.graph_page_button = Button(self.page_button_toolbar, text="Graph Page", width=main_b_width, height=main_b_height, command=lambda: self.show_frame(GraphPage))
self.graph_page_button.pack(side=LEFT, anchor=CENTER, pady=page_button_pady)
self.exit_app_button = Button(self.page_button_toolbar, text="Exit App", width=main_b_width, height=main_b_height, command=lambda: ScientificPumpGui.exit_app(0))
self.exit_app_button.pack(side=LEFT, anchor=CENTER, pady=page_button_pady)
class MonitorPage(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.monitor_data_counter = 0
self.page_label = tk.Label(self, text="Monitor Page")
self.page_label.pack(pady=10, padx=10)
def value_function(self):
self.temperature = 100
class GraphPage(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Graph Page!")
label.pack(pady=5, padx=10)
app = ScientificPumpGui()
app.mainloop()
When I tried to read temperature using:
monitor_page=MonitorPage(ScientificPumpGui())
print(str(monitor_page.temperature))
monitor_page.mainloop()
The error I get is:
AttributeError: 'MonitorPage' object has no attribute 'temperature'
Your MonitorPage class does not declaretemperature in the constructor function, but in value_function.
You can either declare temperature inside __init__ function, or call value_function before reading temperature.
You get this error because the member temperature is initialized in value_function method, which is not getting called.
As you didn't called to this method, the member temperature is not initialized and therefore you get the error.
In order to prevent this error, you should define the member temperature in your __init__ method with a default value.
You also can fix it by calling to the value_function method in order to initialize the member temperature.
Related
This is the window provides the container and methods which allow frame swapping:
class Login_Window(ctk.CTk):
def __init__(self, *args, **kwargs):
super().__init__()
self.geometry('400x400')
self.title('Music Mayhem')
self.resizable(False, False)
container = ctk.CTkFrame(master=self)
container.pack(side='top', fill='both', expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames={}
for F in (LoginFrame, RegEmailFrame):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky= 'nsew')
frame.grid_columnconfigure(0,weight=1)
frame.grid_rowconfigure(0,weight=1)
self.show_frame(LoginFrame)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
In order to swap the frames, a button has to be created in the frame that is going to be swapped. How would i go about creating an instance of another class within these frames which will also call the show_frame method? Here is the code for the frames- this could be ran as long as you have tkinter and custom tkinter installed. The only aspect that should supposedly not work are the buttons in the menu frame.
Yes, in this situation the menu frame is not needed but this is just a simple example because the actual code is way too long to be included here.
I have tried adding the menu frame into the list of frames to be swapped (in the class above) and giving it the same parent and controller attributes as the other frame but that required a parent and controller argument to be passed through when it is called in the Login and Register frames.
Is there a way to get round this or a simpler method that could be implemented instead?
class LoginFrame (tk.Frame):
def __init__(self,parent, controller):
tk.Frame.__init__(self, parent)
self.menu = Menu(self)
self.menu.grid(row=0, column=0)
self.loginBtn = ctk.CTkButton(master=self, width=100, height = 20,text='Login',
state='normal',
command=lambda: controller.show_frame(RegEmailFrame)
self.loginBtn.grid(row=1, column=0)
class RegEmailFrame(tk.Frame):
def __init__(self, parent, controller,header_name="Register Email"):
tk.Frame.__init__(self, parent)
self.menu = Menu(self)
self.menu.grid(row=0, column=0)
self.emailLabel = ctk.CtKLabel(master=self,width=100, height=20 text='Frame swapped')
self.emailLabel.grid(row=1, column=0)
class Menu(tk.Frame):
def __init__(self, *args, header_name="Logo Frame",
width=175, height=175,**kwargs):
super().__init__(*args, width=width, height=height, **kwargs)
self.menuloginBtn = ctk.CTkButton(master=self, width=100, height = 20,text='Login',
state='normal',
command=lambda: controller.show_frame(LoginFrame)
self.menuloginBtn.grid(row=0, column=0)
self.menuRegBtn = ctk.CTkButton(master=self, width=100, height = 20,text='Login',
state='normal',
command=lambda: controller.show_frame(RegEmailFrame)
self.menuRegBtn.grid(row=1, column=0)
In the current implementation, the Menu class does not have access to the controller object that is used to switch between frames in the Login_Window class. One way to fix this would be to pass the controller object to the Menu class during instantiation.
You can do this by adding a parameter called controller in the Menu class constructor and then passing it as an argument when creating an instance of the Menu class in the LoginFrame and RegEmailFrame classes.
For example, in the LoginFrame class:
def __init__(self,parent, controller):
tk.Frame.__init__(self, parent)
self.menu = Menu(self, controller)
self.menu.grid(row=0, column=0)
And in the Menu class constructor:
def __init__(self, parent, controller, *args, header_name="Logo Frame",
width=175, height=175,**kwargs):
super().__init__(parent, *args, width=width, height=height, **kwargs)
self.controller = controller
With this changes, the Menu class now has access to the controller object and can use it to switch between frames using the show_frame method.
You should also make the same changes in the RegEmailFrame class and in the constructor of the Menu class.
Hope this helps!
This question already has answers here:
Calling functions from a Tkinter Frame to another
(2 answers)
Closed 1 year ago.
I have a Tkinter application that has multiple class based frames and I would like to call a function which is in tkinter Frame from another frame/class.
For example, my frames are like this:
class_B(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
def Update_class_b():
label = tk.Label(class_B, text=f"Welcome", font=LARGEFONT)
label.grid(row=1, column=0)
# wigdgets
button = tk.Button(self, text="START_TRIP", command=lambda: controller.show_frame(D_file.D_class)
#i am using this controller to navigate between those pages
# packing/grid
button.grid(row=2, column=0, padx=10, pady=10)
Now, I would like to call the function update_class_b from another class base frame. How can I do that right now I am passing class_B in label widget while making it as you can see and directly calling it in class_A, but it is not working. Please if anyone could help me regarding this.
Also, I would like to call this update_class_b function from inside another class based frame like while pressing a button in Class_A this function should be triger
class A would be like
class class_A(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
def do_something_in_class_B():
#this should trigger Update_class_b function in class_B
button = tk.Button(self, text="DO SOMETHING IN CLASS B", command=do_something_in_class_B).pack()
controller class
class tkinterApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
self.frames = {}
self.attributes('-fullscreen', True)
for F in (file_A.Class_A,file_B.class_B):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(startPage.StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
if __name__ == "__main__":
app = tkinterApp()
app.mainloop()
Assuming you have your frames stored in a dictionary in the controller frame:
func = controller.frames[class_B].update_class_b
button = tk.Button(self, text="START_TRIP", command=func)
button.grid(row=2, column=0, padx=10, pady=10)
We'd have to see a MCVE to give a specific answer.
I am new to Python and not very experienced with classes, however working on the creation of a tkinter GUI for data processing right now.
As many time consuming processes are happening in the background not visible for the user, I would like to insert a progress-bar that shows the current progress between 0 and 100 as Progress and the processing step Action in the main window
Right now, I have problems to access the bar parameters (value and label/name) outside of the class when the code is doing the data processing in a different function.
Below is a working example of the GUI in Python 3.7
import time
import tkinter as tk
from tkinter import ttk
def ProcessingScript():
### UpdateProgressbar(50, 'Halfway there') ###
time.sleep(2)
print('Processing takes place here')
### UpdateProgressbar(75, 'Finishing up') ###
time.sleep(2)
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self, width=500, height=500)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.geometry("500x500")
self.frames = {}
frame = ProcessingPage(container, self)
self.frames[ProcessingPage] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(ProcessingPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class ProcessingPage(tk.Frame):
def __init__(self, parent, controller, ):
tk.Frame.__init__(self, parent)
self.controller = controller
def PlotData():
UpdateProgressbar(10, 'Generating...')
# Execute Main Plotting Function here
ProcessingScript()
UpdateProgressbar(100, 'Finished Plot')
def UpdateProgressbar(Progress, Action):
progressLabel = tk.Label(self, text=Action).place(x=20, y=440)
progressBar['value'] = Progress
progressBar = ttk.Progressbar(self, orient="horizontal", length=200,mode="determinate")
progressBar.place(x=20, y=470)
progressBar['value'] = 0
progressLabel = tk.Label(self, text='Idle...').place(x=20, y=440)
PlotButton = tk.Button(self, text='Plot Data',command= PlotData)
PlotButton.place(x=20, y=320)
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
In this example, the ProcessingScript function would be in a different file and as an ideal outcome I would like to be able to call the UpdateProgressbar function from anywhere in my other scripts to update the bar.
Note: I am aware that a function inside the __init__ function is not best practice, however I was not able to get it running in any other way as I found no way to connect the results of the UpdateProgressbar function with the progressBar created.
Any help to achieve this and exclude UpdateProgressbar from __init__ is much appreciated.
EDIT:
Below is a working version based on the input from the comments. It might now be very pretty but is currently doing what I expect it do to. Please let me know if you see some possibilities for improvement.
app.update() has to be called after each change in the progress bar to show the error and old labels are deleted with self.progressLabel.destroy().
timeit.sleep() is simply a way of showing the changes and will not be part of the final code.
import time
import tkinter as tk
from tkinter import ttk
def ProcessingScript(self, callback):
ProcessingPage.UpdateProgressbar(self, 50, 'Halfway there')
time.sleep(2)
print('Processing takes place here')
ProcessingPage.UpdateProgressbar(self, 75, 'Finishing up')
time.sleep(2)
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self, width=500, height=500)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.geometry("500x500")
self.frames = {}
frame = ProcessingPage(container, self)
self.frames[ProcessingPage] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(ProcessingPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class ProcessingPage(tk.Frame):
def __init__(self, parent, controller, ):
tk.Frame.__init__(self, parent)
self.controller = controller
progressBar = ttk.Progressbar(self, orient="horizontal", length=200,mode="determinate")
progressBar.place(x=20, y=470)
progressBar['value'] = 0
self.progressLabel = tk.Label(self, text='Idle...')
self.progressLabel.place(x=20, y=440)
PlotButton = tk.Button(self, text='Plot Data',command= self.PlotData)
PlotButton.place(x=20, y=320)
def PlotData(self):
self.UpdateProgressbar(10, 'Generating...')
app.update()
time.sleep(2)
# Execute Main Plotting Function here
ProcessingScript(self, self.UpdateProgressbar)
self.UpdateProgressbar(100, 'Finished Plot')
app.update()
def UpdateProgressbar(self, Progress, Action):
self.progressLabel.destroy()
self.progressLabel = tk.Label(self, text=Action)
self.progressLabel.place(x=20, y=440)
progressBar = ttk.Progressbar(self, orient="horizontal", length=200,mode="determinate")
progressBar.place(x=20, y=470)
progressBar['value'] = Progress
app.update()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
I am making an app for a project called the organizer. It is an organization app. I have an issue that when you check one check box, they all check off.
How do I fix this?
After ran go to: Checklist -> Enter A Value -> Click "Add Assignment" -> Repeat A few times -> try to click one
Also, my .update() works, but still seems to cause an error? Do you know why?
Thanks!
import tkinter as tk
root = tk
AsgnList = []
#Initialization
class TheOrganizer(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
self.geometry('500x500')
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, AddAsgnPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def RunApp():
app = TheOrganizer()
app.title('The Organizer')
app.mainloop()
#Making New Pages
'''
Make sure for very new page, you add it to the 'for loop'
'''
HeadFont = ("Verdana", 40)
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
LabTitle = tk.Label(self, text="The Organizer", font=HeadFont)
LabTitle.pack()
AddAsgnBtn = tk.Button(self, text='Checklist', command=lambda: controller.show_frame(AddAsgnPage))
AddAsgnBtn.place(x=100,y=250)
class AddAsgnPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
LabTitle = tk.Label(self, text="Assignments", font=HeadFont)
LabTitle.pack()
#Input Assignment
def getEntry():
entryInput = AsgnEntry.get()
AsgnList.append(entryInput)
yVal = 300
while 1:
var = tk.IntVar()
for z in AsgnList:
tk.Checkbutton(self, text=z, variable=var).place(x=200,y=yVal)
yVal += 25
TheOrganizer.update()
AsgnEntry = tk.Entry(self)
AsgnEntry.place(x=175,y=205)
SubBtn = tk.Button(self, text='Add Assignment', command=getEntry)
SubBtn.place(x=25,y=200)
BackBtn = tk.Button(self, text='Back', command=lambda: controller.show_frame(StartPage))
BackBtn.place(x=250,y=400)
You are using the same variable for both checkboxes. Move the variable creation to inside the loop, to use different variables.
var_list = []
for z in AsgnList:
var = tk.IntVar()
tk.Checkbutton(self, text=z, variable=var).place(x=200,y=yVal)
yVal += 25
var_list.append(var)
That said, you probably want to store the variables so you can check which checkboxes are marked later. So I added a var_list list object to store all created vars.
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()))