I have a program that deals a lot with creating objects from data in files with the ability to edit the objects and then save them in the same file. I am implementing a GUI, and I am using tkinter to do it. The problem I am facing is the problem of frames not updating when I jump back and forth between frames, since the constructor method only runs once when the program begins, and which is where I produce most of the widgets on the screen. Below is an example of what I would like to accomplish:
import tkinter as tk
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
container = tk.Frame(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 (Homescreen, Menuscreen):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
self.show_frame(Homescreen)
def show_frame(self, container):
frame = self.frames[container]
frame.tkraise()
class Homescreen(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
with open("test.txt", "r") as f:
text = f.readline()
tk.Label(self, text = text).pack()
tk.Button(self, text = "next page", command = lambda: controller.show_frame(Menuscreen)).pack()
class Menuscreen(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.entry = tk.StringVar()
tk.Entry(self, textvariable = self.entry).pack()
tk.Button(self, text = "back to page", command = self.writeToFile).pack()
def writeToFile(self):
with open("test.txt", "w") as f:
f.writelines(self.entry.get())
self.controller.show_frame(Homescreen)
app = App()
app.geometry("500x400")
app.mainloop()
If I have a textfile with just a simple word, in Homescreen i print it out on the screen. Then I move to the second frame, Menuscreen, where I allow the user to enter another word, and then I store the word in the same textfile. Then the program takes us back to the Homescreen, but the problem is that the printed out word, will still be the first word and not the updated word in the textfile.
I tried to use the methods .update() and .destroy(), one line before i execute frame.tkraise(), but the .update() method doesn't do anything and when I use the .destroy() method, I get an error saying _tkinter.TclError: bad window path name
You can't get the "printed out word" to change because the constructor for Homescreen is only run once. You need another method that changes the entry when you raise the Frame. The changes are commented below. There are only 4.
import tkinter as tk
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
container = tk.Frame(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 (Homescreen, Menuscreen):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
self.show_frame(Homescreen)
def show_frame(self, container):
frame = self.frames[container]
#update the label
if container is Homescreen: frame.update_label()
frame.tkraise()
class Homescreen(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
#keep a reference to the label so it can be modified
self.label = tk.Label(self)
self.label.pack()
tk.Button(self, text = "next page", command = lambda: controller.show_frame(Menuscreen)).pack()
self.update_label()
#update the label
def update_label(self):
with open('test.txt', 'r') as f:
self.label['text'] = f.read()
class Menuscreen(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.entry = tk.StringVar()
tk.Entry(self, textvariable = self.entry).pack()
tk.Button(self, text = "back to page", command = self.writeToFile).pack()
def writeToFile(self):
with open('test.txt', 'w') as f:
f.write(self.entry.get())
#go back to homescreen
self.controller.show_frame(Homescreen)
app = App()
app.geometry("500x400")
app.mainloop()
If you'd like to make your script a little easier to work with, I refactored it below, and left comments.
import tkinter as tk
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
#you do not need `container`
#using root as the master makes it a more natural controller
#you were using it more like a proxy
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
#you created a convention with your class names of using "screen"
#use some kind of "screen" for everything that contains or directly relates to them
self.screens = {}
#don't "juggle"
#create a var, and use it
for screen in (Menuscreen, Homescreen):
self.screens[screen] = screen(self)
self.screens[screen].grid(row=0, column=0, sticky="nsew")
#renamed to reflect that it relates to your screens
def show_screen(self, screen):
target = self.screens[screen]
if screen is Homescreen: target.update_label()
target.tkraise()
class Homescreen(tk.Frame):
#whatever you put in master becomes the `self.master` of this widget
#ie.. we are keeping the names the same
def __init__(self, master, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
self.label = tk.Label(self)
self.label.pack()
#this line illustrates how the root is the controller
tk.Button(self, text="next page", command=lambda: master.show_screen(Menuscreen)).pack()
#init the label text
self.update_label()
def update_label(self):
with open('test.txt', 'r') as f:
self.label['text'] = f.read()
class Menuscreen(tk.Frame):
def __init__(self, master, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
self.entry = tk.StringVar()
tk.Entry(self, textvariable=self.entry).pack()
tk.Button(self, text="back to page", command=self.writeToFile).pack()
def writeToFile(self):
with open('test.txt', 'w') as f:
f.write(self.entry.get())
#illustrates again how the root is the controller
self.master.show_screen(Homescreen)
#this is good practice
if __name__ == "__main__":
app = App()
app.geometry("500x400")
app.mainloop()
Related
Pardon me for my bad grammar or explanation, since I didn't know how to explain this properly.
I try to build some gui that could switch between frame, using script from this as base Switch between two frames in tkinter.
In this case, I will have a few frame that had similar design, but different function when the button is pressed. For example, I have 2 frames that have similar 2 entries and 1 button, but the button do different command (where at sub01 frame it will multiply and at sub02 frame will divide)
This is my code:
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.grid(row=1,columnspan=4,sticky='nsew')
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (sub01, sub02):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=1,sticky="nsew")
self.choices = {'sub01','sub02'}
self.tkvar = tk.StringVar()
self.tkvar.set('sub01')
self.popMenu = tk.OptionMenu(self,self.tkvar,*self.choices)
self.popMenu.grid(row=0)
self.show_frame()
self.button1 = tk.Button(self, text="Go to Layer",command=lambda: self.show_frame())
self.button1.grid(row=0, column=1)
def show_frame(self):
'''Show a frame for the given page name'''
page_name = self.tkvar.get()
frame = self.frames[page_name]
frame.tkraise()
class sub01(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This SubLayer 1")
label.grid(row=0)
self.entries=[]
i = 0
while i < 2:
self.entries.append(tk.Entry(self,width=10))
self.entries[i].grid(row=i+1,columnspan=2,sticky='we')
i += 1
self.btn = tk.Button(self,text="multiply", command=lambda : self.multiply())
self.btn.grid(row=i+1, columnspan=2,sticky='we')
def multiply(self):
pass
class sub02(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This SubLayer 2")
label.grid(row=0)
self.entries=[]
i = 0
while i < 2:
self.entries.append(tk.Entry(self,width=10))
self.entries[i].grid(row=i+1,columnspan=2,sticky='w')
i += 1
self.btn = tk.Button(self,text="divide",command=lambda : self.divide())
self.btn.grid(row=i+1, columnspan=2,sticky='we')
def divide(self):
pass
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
This code itself works, but when I need to create more of these frames, it becomes inconvenient. How could I make this code simpler? Like having that similar frame as a class, and the button as other class that do differ behaviour depend of the layer shown.
Thank you in advance
The canonical way to do this sort of thing is to create a class hierarchy for your Page classes and put common functionality in the base classes and derive subclasses from them that specify the behavior that differs between them. Below is how you could do that with the sample code in your question.
Since the things that are different between them are:
The text displayed on the Label.
The text displayed on the Button.
The code in that's execute when the Button is clicked.
This means the derived classes only need to know what code to run in a generically named btn_func() method and what the text to displayed on the two widgets. The code below illustrates how to do that.
Note that I've changed the spelling of your class names to conform to the naming conventions describe in PEP 8 - Style Guide for Python Code.
import Tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.grid(row=1,columnspan=4,sticky='nsew')
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (Sub01, Sub02):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=1,sticky="nsew")
self.choices = {'Sub01','Sub02'}
self.tkvar = tk.StringVar()
self.tkvar.set('Sub01')
self.popMenu = tk.OptionMenu(self,self.tkvar,*self.choices)
self.popMenu.grid(row=0)
self.show_frame()
self.button1 = tk.Button(self, text="Go to Layer",command=lambda: self.show_frame())
self.button1.grid(row=0, column=1)
def show_frame(self):
'''Show a frame for the given page name'''
page_name = self.tkvar.get()
frame = self.frames[page_name]
frame.tkraise()
class BaseSubLayer(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text=self.lbl_text)
label.grid(row=0)
self.entries=[]
i = 0
while i < 2:
self.entries.append(tk.Entry(self,width=10))
self.entries[i].grid(row=i+1,columnspan=2,sticky='we')
i += 1
self.btn = tk.Button(self,text=self.btn_func_name, command=self.btn_func)
self.btn.grid(row=i+1, columnspan=2,sticky='we')
def btn_func(self):
raise NotImplementedError
class Sub01(BaseSubLayer):
lbl_text = 'This SubLayer 1'
btn_func_name = 'multiply'
def btn_func(self):
print('Running multiply() method.')
class Sub02(BaseSubLayer):
lbl_text = 'This SubLayer 2'
btn_func_name = 'divide'
def btn_func(self):
print('Running divide() method.')
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
This is shortened example. I have 2 frames: Page1 and Page2. prj_conf is default dictionary.
In Page1,
Browse button specify text file, Load button read the text file and update the values of prj_conf from the text file. Next button jumps to Page2.
In Page2, Entryproj entry shows the value of prj_conf["_cal_input_pwr"].
The target is when I load text file and update the values of prj_conf, then press Next button in Page1. my GUI jumps to Page2 and the value of prj_conf["_cal_input_pwr"] in Entryproj entry should be come from the text file.
However there are many widgets in original Page2, so I want to update all widget values in Page2 when Next button is pressed. How do I do that?
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
from tkinter import messagebox as mBox
# create dafault dictionary
prj_keys = ["_time", "_projet", "_DUT", "_user", "_project_dir", "_cal_input_pwr"]
prj_conf = {key: "0" for key in prj_keys}
#read text file
def read_settingfile():
print("test")
class main_win(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.geometry(self)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand = True)
container.rowconfigure(0, weight=1)
container.columnconfigure(0, weight=1)
self.frames = {}
for F,geometry in zip(( Page1, Page2), ( "900x617"
, "993x817")):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = (frame, geometry)
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("Page1")
def show_frame(self, page_name):
frame, geometry = self.frames[page_name]
self.geometry(geometry)
frame.tkraise()
class Page1(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.configure(bg = "#d8d8d8")
self.controller = controller
self.labelframe_newfile = tk.LabelFrame(self)
self.loadLabelframe = tk.LabelFrame(self)
self.button_next = tk.Button(self, command = lambda: self.setnextdata())
self.button_next.place(relx=0.811, rely=0.891, height=41, width=150)
self.button_next.configure(activebackground="#ececec")
self.button_next.configure(activeforeground="#000000")
self.button_next.configure(background="#d9d9d9")
self.button_next.configure(disabledforeground="#a3a3a3")
self.button_next.configure(foreground="#000000")
self.button_next.configure(highlightbackground="#d9d9d9")
self.button_next.configure(highlightcolor="black")
self.button_next.configure(pady="0")
self.button_next.configure(text='''Next''')
self.loadLabelframe.place(relx=0.033, rely=0.25, relheight=0.6
, relwidth=0.933)
self.loadLabelframe.configure(relief='groove')
self.loadLabelframe.configure(font="-family {微軟正黑體} -size 14")
self.loadLabelframe.configure(foreground="black")
self.loadLabelframe.configure(text='''Load File''')
self.loadLabelframe.configure(background="#d9d9d9")
self.loadLabelframe.configure(highlightbackground="#d9d9d9")
self.loadLabelframe.configure(highlightcolor="black")
self.loadprojfile_path = tk.StringVar()
self.Entry_loadprojfile = tk.Entry(self.loadLabelframe, textvariable = self.loadprojfile_path)
self.Entry_loadprojfile.place(relx=0.048, rely=0.723, height=31, relwidth=0.755
, bordermode='ignore')
self.Entry_loadprojfile.configure(background="white")
self.Entry_loadprojfile.configure(disabledforeground="#a3a3a3")
self.Entry_loadprojfile.configure(font="TkFixedFont")
self.Entry_loadprojfile.configure(foreground="#000000")
self.Entry_loadprojfile.configure(highlightbackground="#d9d9d9")
self.Entry_loadprojfile.configure(highlightcolor="black")
self.Entry_loadprojfile.configure(insertbackground="black")
self.Entry_loadprojfile.configure(selectbackground="#c4c4c4")
self.Entry_loadprojfile.configure(selectforeground="black")
self.button_browse_proj_fle = tk.Button(self.loadLabelframe,
command = lambda:self.dir_browse_old())
self.button_browse_proj_fle.place(relx=0.82, rely=0.723, height=31
, width=65, bordermode='ignore')
self.button_browse_proj_fle.configure(activebackground="#ececec")
self.button_browse_proj_fle.configure(activeforeground="#000000")
self.button_browse_proj_fle.configure(background="#d9d9d9")
self.button_browse_proj_fle.configure(disabledforeground="#a3a3a3")
self.button_browse_proj_fle.configure(foreground="#000000")
self.button_browse_proj_fle.configure(highlightbackground="#d9d9d9")
self.button_browse_proj_fle.configure(highlightcolor="black")
self.button_browse_proj_fle.configure(pady="0")
self.button_browse_proj_fle.configure(text='''Browse''')
self.button_load = tk.Button(self.loadLabelframe, command = lambda:self.loadfile())
self.button_load.place(relx=0.912, rely=0.723, height=31, width=49
, bordermode='ignore')
self.button_load.configure(activebackground="#ececec")
self.button_load.configure(activeforeground="#000000")
self.button_load.configure(background="#d9d9d9")
self.button_load.configure(disabledforeground="#a3a3a3")
self.button_load.configure(foreground="#000000")
self.button_load.configure(highlightbackground="#d9d9d9")
self.button_load.configure(highlightcolor="black")
self.button_load.configure(pady="0")
self.button_load.configure(text='''Load''')
def setnextdata(self):
self.controller.show_frame("Page2")
def dir_browse_old(self):
self.old_work_directory = filedialog.askopenfilename( filetypes = (("project files", "*.prj"),("all files","*.*")) )
self.loadprojfile_path.set(self.old_work_directory)
def loadfile(self):
read_settingfile(self.old_work_directory)
print("load"+str(prj_conf["_cal_input_pwr"]))
class Page2(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.configure(bg = "#d8d8d8")
self.TSeparator1 = ttk.Separator(self)
self.TSeparator1.place(relx=0.005, rely=0.092, relwidth=0.987)
self.label_title = tk.Label(self)
self.label_title.place(relx=0.07, rely=0.018, height=55, width=793)
self.label_title.configure(activebackground="#f9f9f9")
self.label_title.configure(activeforeground="black")
self.label_title.configure(background="#d9d9d9")
self.label_title.configure(disabledforeground="#a3a3a3")
self.label_title.configure(font="-family {新細明體} -size 30")
self.label_title.configure(foreground="#000000")
self.label_title.configure(highlightbackground="#d9d9d9")
self.label_title.configure(highlightcolor="black")
self.label_title.configure(text='''Step 2. Calibration Setting''')
self.button_last = tk.Button(self, command = lambda: self.last_update())
self.button_last.place(relx=0.03, rely=0.93, height=41, width=150)
self.button_last.configure(activebackground="#ececec")
self.button_last.configure(activeforeground="#000000")
self.button_last.configure(background="#d9d9d9")
self.button_last.configure(disabledforeground="#a3a3a3")
self.button_last.configure(foreground="#000000")
self.button_last.configure(highlightbackground="#d9d9d9")
self.button_last.configure(highlightcolor="black")
self.button_last.configure(pady="0")
self.button_last.configure(text='''Last''')
self.inpu_pow_leve_frame = tk.Frame(self)
self.inpu_pow_leve_frame.place(relx=0.081, rely=0.208, relheight=0.092
, relwidth=0.846)
self.inpu_pow_leve_frame.configure(relief='groove')
self.inpu_pow_leve_frame.configure(borderwidth="2")
self.inpu_pow_leve_frame.configure(relief="groove")
self.inpu_pow_leve_frame.configure(background="#d9d9d9")
self.inpu_pow_leve_frame.configure(highlightbackground="#d9d9d9")
self.inpu_pow_leve_frame.configure(highlightcolor="black")
self.label_inpu_pow_leve = tk.Label(self.inpu_pow_leve_frame)
self.label_inpu_pow_leve.place(relx=0.036, rely=0.267, height=36
, width=271)
self.label_inpu_pow_leve.configure(activebackground="#f9f9f9")
self.label_inpu_pow_leve.configure(activeforeground="black")
self.label_inpu_pow_leve.configure(background="#d9d9d9")
self.label_inpu_pow_leve.configure(disabledforeground="#a3a3a3")
self.label_inpu_pow_leve.configure(font="-family {微軟正黑體} -size 14")
self.label_inpu_pow_leve.configure(foreground="#000000")
self.label_inpu_pow_leve.configure(highlightbackground="#d9d9d9")
self.label_inpu_pow_leve.configure(highlightcolor="black")
self.label_inpu_pow_leve.configure(text='''Input Power Level (dBm)''')
self.var_inpu_pow_leve = tk.IntVar()
self.var_inpu_pow_leve.set(prj_conf["_cal_input_pwr"])
self.Entryproj = tk.Entry(self.inpu_pow_leve_frame,textvariable = self.var_inpu_pow_leve)
self.Entryproj.place(relx=0.383, rely=0.267,height=31, relwidth=0.58)
self.Entryproj.configure(background="white")
self.Entryproj.configure(disabledforeground="#a3a3a3")
self.Entryproj.configure(font="TkFixedFont")
self.Entryproj.configure(foreground="#000000")
self.Entryproj.configure(highlightbackground="#d9d9d9")
self.Entryproj.configure(highlightcolor="black")
self.Entryproj.configure(insertbackground="black")
self.Entryproj.configure(selectbackground="#c4c4c4")
self.Entryproj.configure(selectforeground="black")
def last_update(self):
self.controller.show_frame("Page1")
app = main_win()
app.mainloop()
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()))
Im trying to code a simple tkinter program that returns reddit information back to the user. In doing so, I'm receiving the error message:
Traceback (most recent call last):
File "redditscraper4.py", line 111, in <module>
app = RedditScraper()
File "redditscraper4.py", line 23, in __init__
frame = F(container, self)
File "redditscraper4.py", line 93, in __init__
get_user_entry_string = get_user_entry.addBrackets()
File "redditscraper4.py", line 62, in addBrackets
user_entry = StartPage()
TypeError: __init__() missing 2 required positional arguments: 'parent' and 'controller'
I have absolutely no clue as to what I've done wrong with my code. Im lost, and nowhere on the Web seems to have a coherent answer to this problem.
Here is my code:
import tkinter as tk
from functools import partial
from webbrowser import open
from datetime import date
import praw
'''Initialising the Applicaiton'''
class RedditScraper(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)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, redditReturn):
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()
'''The First Page the User will see'''
class StartPage(tk.Frame, object):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label1 = tk.Label(self, text="Start Page")
label1.pack(pady=10, padx=10)
button1 = tk.Button(self, text="Scrape This Subreddit", command=lambda: controller.show_frame(redditReturn))
button1.pack(pady=10, padx=10)
self.entry_var = tk.StringVar()
e1 = tk.Entry(self,textvariable=self.entry_var)
e1.pack(pady=10, padx=10)
StartPage.entry1 = self.entry_var.get()
'''Adding brackets around the user's entry to the label to suffice the praw api'''
class bracketEntry(object):
def addBrackets(self):
user_entry = StartPage()
get_user_entry_string = user_entry.entry1()
user_entry_plus_brackets = '"' + get_user_entry_string + '"'
print(user_entry_plus_brackets)
return user_entry_plus_brackets
'''Collecting data from reddit'''
class redditCollect(object):
def getSubreddit(self):
user_agent = "Simple Subreddit Scraper"
r = praw.Reddit(user_agent=user_agent)
'''remember to add the ability to get the user-defined subreddit information'''
user_entry = bracketEntry()
user_entry_variable = user_entry.addBrackets()
posts = r.get_subreddit("pics").get_hot(limit = 10)
return posts
'''The window containing the information from Reddit for the user'''
class redditReturn(tk.Frame, object):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
"""Creates all the buttons and frames for the GUI"""
get_user_entry = bracketEntry()
get_user_entry_string = get_user_entry.addBrackets()
intro = get_user_entry_string + " on Reddit: "
newFrame = tk.LabelFrame(self, text = intro)
newFrame.pack(fill="both", expand= True , anchor="nw")
row = 0
redditCollectGetter = redditCollect()
local_posts = redditCollectGetter.getSubreddit()
for p in local_posts:
gotoArticle = partial(open, p.url)
title = "(" + str(p.score) +") " + p.title
tk.Label(newFrame, text= title, pady= 10, wraplength= 700, justify= "left").grid(row= row, column= 0, sticky= "w")
tk.Button(newFrame, text= "Read more!", command= gotoArticle).grid(row= row+1, column= 0, sticky= "w")
tk.row = row + 2
app = RedditScraper()
app.mainloop()
Any help is appreciated!
Thanks!
In your bracketEntry class, in the addBrackets method, you call user_entry = StartPage(). However, you declare StartPage's __init__ method as def __init__(self, parent, controller):, which means you have to provide parent and controller arguments.
Edit: To fix the method, you are going to have to pass the parent and controller objects all the way down the call stack or find another way of getting them into the addBrackets method. E.g., you could redefine def addBrackets(self, parent, controller), and then update the infringing line: user_entry = StartPage(parent, controller). You would then have to update all calls to addBracket to include the new argument.
You've defined the __init__ method of StartPage to take two required arguments, parent and controller. But in the line that's causing the error, you are just calling StartPage() without passing those arguments. As the error says, you need to pass them.
import tkinter as tk
from functools import partial
from webbrowser import open
from datetime import date
import praw
'''Initialising the Applicaiton'''
class RedditScraper(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)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, redditReturn):
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()
'''The First Page the User will see'''
class StartPage(tk.Frame, object):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label1 = tk.Label(self, text="Start Page")
label1.pack(pady=10, padx=10)
button1 = tk.Button(self, text="Scrape This Subreddit", command=lambda: controller.show_frame(redditReturn))
button1.pack(pady=10, padx=10)
self.entry_var = tk.StringVar()
e1 = tk.Entry(self, textvariable=self.entry_var)
e1.pack(pady=10, padx=10)
global save
save = tk.StringVar()
save = e1.get()
# StartPage.entry1: str = self.entry_var.get()
def entry1(self):
global save
user_input = tk.StringVar()
user_input = save
return save
'''Adding brackets around the user's entry to the label to suffice the praw api'''
class bracketEntry(object):
def addBrackets(self, parent, controller):
user_entry = StartPage(parent, controller)
# * Takes in inputs as parent and the controller
get_user_entry_string = user_entry.entry1()
user_entry_plus_brackets = '"' + get_user_entry_string + '"'
print(user_entry_plus_brackets)
return user_entry_plus_brackets
'''Collecting data from reddit'''
class redditCollect(object):
def getSubreddit(self, parent, controller):
user_agent = "Simple Subreddit Scraper"
r = praw.Reddit(user_agent=user_agent)
'''remember to add the ability to get the user-defined subreddit information'''
user_entry = bracketEntry()
user_entry_variable = user_entry.addBrackets(parent,controller)
print(user_entry_variable) # prints the quoted string in the Entry field
posts = r.get_subreddit("pics").get_hot(limit=10)
return posts
'''The window containing the information from Reddit for the user'''
class redditReturn(tk.Frame, object):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
"""Creates all the buttons and frames for the GUI"""
get_user_entry = bracketEntry()
# * takes in no inputs
get_user_entry_string = get_user_entry.addBrackets(parent, controller)
intro = get_user_entry_string + " on Reddit: "
newFrame = tk.LabelFrame(self, text=intro)
newFrame.pack(fill="both", expand=True, anchor="nw")
row = 0
redditCollectGetter = redditCollect()
local_posts = redditCollectGetter.getSubreddit(parent,controller)
for p in local_posts:
gotoArticle = partial(open, p.url)
title = "(" + str(p.score) + ") " + p.title
tk.Label(newFrame, text=title, pady=10, wraplength=700, justify="left").grid(row=row, column=0, sticky="w")
tk.Button(newFrame, text="Read more!", command=gotoArticle).grid(row=row + 1, column=0, sticky="w")
tk.row = row + 2
app = RedditScraper()
How would I create a new window when the user clicks a button (still needs creating)? I have took some code out to make this shorter. I need a button creating and when they hit that button, a new window opens. I haven't created the button because the button has to be linked to the new window. Please help
My imports...
class App:
def __init__(self, master):
self.master = master
# call start to initialize to create the UI elemets
self.start()
def start(self):
self.master.title("E-mail Extranalyser")
self.now = datetime.datetime.now()
tkinter.Label(
self.master, text=label01).grid(row=0, column=0, sticky=tkinter.W)
# CREATE A TEXTBOX
self.filelocation = tkinter.Entry(self.master)
self.filelocation["width"] = 60
self.filelocation.focus_set()
self.filelocation.grid(row=0, column=1)
# CREATE A BUTTON WITH "ASK TO OPEN A FILE"
# see: def browse_file(self)
self.open_file = tkinter.Button(
self.master, text="Browse...", command=self.browse_file)
# put it beside the filelocation textbox
self.open_file.grid(row=0, column=2)
# now for a button
self.submit = tkinter.Button(
self.master, text="Execute!", command=self.start_processing,
fg="red")
self.submit.grid(row=13, column=1, sticky=tkinter.W)
def start_processing(self):
#code here
def browse_file(self):
# put the result in self.filename
self.filename = filedialog.askopenfilename(title="Open a file...")
# this will set the text of the self.filelocation
self.filelocation.insert(0, self.filename)
root = tkinter.Tk()
app = App(root)
root.mainloop()
Use a Toplevel to open a new one. Modify your code as shown below.
self.NewWindow = tkinter.Button(self.master,
text="New Window",
command=self.CreateNewWindow)
def CreateNewWindow(self):
self.top = tkinter.Toplevel()
self.top.title("title")
Take a look at https://www.youtube.com/watch?v=jBUpjijYtCk. Working through this tutorial would probably help you but this specific video shows how to work with multiple pages.
Something like this:
from tkinter import *
class Sample(Tk):
def __init__(self,*args, **kwargs):
Tk.__init__(self, *args, **kwargs)
container = Frame(self)
container.pack(side="top", fill="both", expand = True)
self.frames = {}
for F in (MainPage, OtherPage):
frame=F(container, self)
self.frames[F]=frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(MainPage)
def show_frame(self, page):
frame = self.frames[page]
frame.tkraise()
class MainPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
Label(self, text="Start Page").pack()
Button(self, text="other page?", command=lambda:controller.show_frame(OtherPage)).pack()
class OtherPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
Label(self, text="Next Page").pack()
Button(self, text="back", command=lambda:controller.show_frame(MainPage)).pack()
app = Sample()
app.mainloop()