I've got the following application which consists of a menu bar and a frame. On a button press the content of the frame should be changed.
Here's my code so far:
import tkinter as tk
class ImageTool(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.grid(row=0,column=0,sticky="nsew")
container.grid_rowconfigure(1, weight=1)
container.grid_columnconfigure(2, weight=1)
menu = SideMenu(container,self)
menu.grid(row=0,column=0, sticky="nsew")
self.frames = {}
frame = StartPage(container, self)
backuppage = BackupPage(container,self)
self.frames[StartPage] = frame
self.frames[BackupPage] = backuppage
frame.grid(row=0, column=1, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
print(self)
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
startpage_lbl = tk.Label(self, text="Start Page")
startpage_lbl.pack(pady=10,padx=10,fill="both")
self.winfo_toplevel().title("Image")
class BackupPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
self.winfo_toplevel().title(" Backup")
class SideMenu(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
self.btn_backup = tk.Button(self,text="BACKUP", command= controller.show_frame(BackupPage))
self.btn_restore = tk.Button(self,text="RESTORE")
self.btn_backup.pack(side = tk.TOP, fill= tk.X, expand = True )
self.btn_restore.pack(side = tk.TOP, fill= tk.X, expand = True )
app = ImageTool()
app.mainloop()
My problem is as follows:
When I try to run the code above, I get an error that
AttributeError: '_tkinter.tkapp' object has no attribute 'frames'
However, if I'm not entirely mistaken, I pass the instance of ImageTool to the class SideMenu as controller which is why the command call to controller.show_frame(BackupPage) should work in theory.
What is my mistake?
Thank you very much in advance for any help.
Okay, I figured it out on my own.
There were multiple issues:
The command has to be wrapped in a lambda expression like this:
self.btn_backup = tk.Button(self,text="BACKUP", command= lambda: controller.show_frame(BackupPage))
In order for frame.tkraise() in the show_frame() method to work, the frame has to be loaded on to the grid first:
frame.grid(row=0, column=1, sticky="nsew")
backuppage.grid(row=0, column=1, sticky="nsew")
Related
I am having trouble with passing the variables into different classes. I am a beginner. I am trying to get the entry box to pass the textvariable which is self.margaritapizza(). However, when I try run it, it says 'nextage' has no attribute to 'margaritapizza'. So not sure how to pass the variable to different classes. I am using tkinter and it is in visual studio code if that helps.
import tkinter as tk
from tkinter import IntVar
class SampleApp(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.margaritapizza = IntVar()
self.frames = {}
for F in (StartPage, nextpage):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
frame = self.frames[page_name]
'''Show a frame for the given page name'''
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent, bg='#3d3d5c')
self.controller = controller
heading_label = tk.Label(self,
text="start page",
font=('arial', 45, 'bold'),
foreground='#ffffff',
background='#3d3d5c')
heading_label.pack(pady=25)
def next():
controller.show_frame('nextpage')
next_button = tk.Button(text="start page",command=next).pack
class nextpage(tk.Frame,SampleApp):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent, bg='#3d3d5c')
self.controller = controller
heading_label = tk.Label(self,
text="start page",
font=('arial', 45, 'bold'),
foreground='#ffffff',
background='#3d3d5c')
heading_label.pack(pady=25)
entry = tk.entry(borderwidth=2,width=15,textvariable=self.margaritapizza).pack
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
First, nextpage should definitely not inherit from SampleApp. So, change the definition to:
class nextpage(tk.Frame):
Second, margaritapizza is an attribute of SampleApp, so you need to refer to it via the instance of SampleApp. In your case, self.controller is that instance, so you can use self.controller.margaritapizza.
There are other problems with how you define the entry, I'll fix them as well:
self.entry = tk.Entry(self, borderwidth=2,width=15,textvariable=self.controller.margaritapizza)
self.entry.pack()
I am getting:
print (p.s) AttributeError: StartPage instance has no attribute 's'
here is my code:
import Tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.s = []
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
container = tk.Frame(self)
container.grid(row=0, column=0, sticky='we')
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, images_result):
page_name = F.__name__
frame = F(parent=container, controller=self)
frame.grid(row=0, column=0, sticky='nsew')
self.frames[page_name] = frame
self.show_frame("StartPage")
def get_SP(self):
return self.frames["StartPage"]
def images(self, page_name):
self.s = [1, 2]
frame = self.frames[page_name]
frame.tkraise()
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.grid(row=0, column=0, sticky='snew')
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.image_btn = tk.Button(self, text="Images", command=lambda: controller.images("images_result"))
self.image_btn.grid(row=0, column=0, sticky='snew')
class images_result(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
p = self.controller.get_SP()
print (p.s)
app = SampleApp()
app.mainloop()
Edited:
I want the function images in SampleApp to update the attribute s of the SampleApp then I want to be able to access it from the class images_results.
I tried to use
print(self.controller.s)
but that gives me [] and by that it seems that the modification that has been done by the function images to the s attribute are not visible to the images_result.
The only class in your code which as an s attribute is SampleApp. The instance of this class is passed as the controller attribute for each page. Thus, to access the s attribute of the SampleApp object from within a page will be through the controller:
print(self.controller.s)
It's important to note that in the code you posted, the __init__ method of images_remote will only run when the app first starts up, so any values set by interacting with the other pages won't be printed by the __init__ function.
I'm trying to make a calculator and it has different frames i.e Basic, Statistics, ..., etc. However, I'm having issues to show each frame.
This is the container for all the frames (I took a code of a previous post as example)
import tkinter as tk
class calculatorframe(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
#----------------CONTAINER FOR ALL THE FRAMES----------
container = tk.Frame(self)
container.pack()
#--------------------- DROPDOWN MENU------------------
tkvar = tk.StringVar()
choices={'Basic Mode','Calculus Mode'} #Options of the dropdown menu
tkvar.set('Basic Mode') #default frame
dropdownmenu =tk.OptionMenu(container, tkvar, *choices)
dropdownmenu.grid(row=2,column=3) #position of the dropdown menu
self.frames = {}
for F in (Basic, Calculus):
page_name = F.__name__
frame = F(parent= container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky= "nsew")
self.show_frame('Basic')
#-------FUNCTION TO SHOW THE CURRENT FRAME
def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()
These are the classes that I created for the basic calculator
class Basic(tk.Frame):
def __init__(self, parent, controller):
#--------------- SCREEN ---------------
tk.Frame.__init__(self, parent)
screen = tk.Entry(self, width=80)
screen.grid(row=3, column=1,columnspan=7) #position of the screen
#------------ BUTTON ---------------------
button7=tk.Button(self, text="7", width=8) #button
button7.grid(row=4,column=1)
#---------------------frame for calculus -------------------------
class Calculus(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
#-----------------------------SCREEN------------
screen=tk.Entry(self, width=50)
screen.pack()
screen.grid(row=3, column=1, pady=20, columnspan=7) #position of the screen
#------------------------BUTTON---------------
go=tk.Button(self, height=1, text="Go") #button
go.grid(row=1, column=8)
if __name__ == "__main__":
app = calculatorframe()
app.mainloop()
I'm aware that I have to keep track of the value of tkvar and that I need to do it using trace() and pass that value to show_frame, however, I don't know where to place it in my code. I tried to put it below the dropdown menu, but I get an error message and I tried after the function show_frame and it did not work either. I'm a bit stuck, I would really appreciate your help, thanks in advance.
The simple solution would be to add a command to your OptionsMenu() function. We will also need to change your class names and your choice options due to how the command argument works here.
For the OptionsMenu() command argument when you tell it to call a method it will automatically pass the value of the selected item in the drop down. So because of this we need to make sure our selection reflect the class names. You can change the choices/classes to be whatever you wish I just used BasicMode and CalculusMode as an example.
The command will automatically pass the value selected so we can use that to call each frame using you show_frame method.
Take a look at the below code and let me know if you have any questions.
import tkinter as tk
class calculatorframe(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack()
tkvar = tk.StringVar()
choices = {'BasicMode', 'CalculusMode'}
tkvar.set('BasicMode')
dropdownmenu = tk.OptionMenu(container, tkvar, *choices, command=self.show_frame)
dropdownmenu.grid(row=2, column=3)
self.frames = {}
for F in (BasicMode, CalculusMode):
page_name = F.__name__
frame = F(parent= container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame('BasicMode')
def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()
class BasicMode(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
screen = tk.Entry(self, width=80)
screen.grid(row=3, column=1, columnspan=7)
button7 = tk.Button(self, text="7", width=8)
button7.grid(row=4,column=1)
class CalculusMode(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
screen = tk.Entry(self, width=50)
screen.pack()
screen.grid(row=3, column=1, pady=20, columnspan=7)
go = tk.Button(self, height=1, text="Go")
go.grid(row=1, column=8)
if __name__ == "__main__":
app = calculatorframe()
app.mainloop()
I managed to build a little GUI, where I can switch between frames related to the following question: Switch between frames in tkinter
I wanted to have a status bar on the Bottom of my GUI and it should stay on every frame! The status bar shows info about buttons when hovering over it!
Not hovered:
Hovered:
As you can see it works, BUT I can't manage to make the status bar update its values when another frame is raised, because I could only manage to create the status bar in one frame class...
class SampleApp(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
self.title_font = font.Font(family='Helvetica', size=18,
weight="bold", slant="italic")
container = 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 (start_frame, cr_frame, db_frame):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.pack()
self.show_frame("start_frame")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
for frame in self.frames.values():
#frame.grid_remove()
frame.pack_forget()
frame = self.frames[page_name]
frame.pack()
if page_name == "start_frame":
frame.winfo_toplevel().geometry("545x200")
if page_name == "cr_frame":
frame.winfo_toplevel().geometry("600x200")
if page_name == "db_frame":
frame.winfo_toplevel().geometry("700x630")
class start_frame(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller = controller
self.btn_cr = Button(self, text="Copyright Analyse", command=lambda: self.controller.show_frame("cr_frame"), width=40)
self.btn_cr.pack(side=LEFT, padx=15, pady=1, ipady=40)
self.btn_db = Button(self, text="Copyright Datenbank", command=lambda: self.controller.show_frame("db_frame"), width=40)
self.btn_db.pack(side=LEFT, pady=1, ipady=40)
###################Here is the statusbar defined + bindings and so on##########
self.lbl_status = Label(self.controller, text="...", border=1, relief=SUNKEN, anchor=W)
self.lbl_status.pack(side=BOTTOM, fill=X, anchor=W)
self.btn_cr.bind("<Enter>", lambda event: self.lbl_status.configure(text="Open copyright analysis window..."))
self.btn_cr.bind("<Leave>", self.leave_bindings)
self.btn_db.bind("<Enter>", lambda event: self.lbl_status.configure(text="Open copyright database..."))
self.btn_db.bind("<Leave>", self.leave_bindings)
class cr_frame(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller = controller
Now, if I switch to page 2 (cr_frame), the status bar is still there because I attached it to the top-level controller (of all frames) but I can't edit it through the cr_frame class...
I don't know how to do it.
First, move the statusbar to the main app since it's part of the app and not part of a page:
class SampleApp(Tk):
def __init__(self, *args, **kwargs):
...
container = Frame(self)
self.lbl_status = Label(self, text="", border=1, relief=SUNKEN, anchor=W)
container.pack(side="top", fill="both", expand=True)
self.lbl_status.pack(side="bottom", fill="x")
Next, add a method to the app for setting the status:
class SampleApp(Tk):
...
def set_status(self, string):
self.lbl_status.configure(text=string)
Finally, call that method whenever you need to change the status:
class cr_frame(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller = controller
...
def something(self):
...
self.controller.set_status("Hello, world")
...
I was hoping someone could tell me where I am going wrong. I would love to be able to access this shared_Data dictionary from any one of my classes, however I get the error "AttributeError: '_tkinter.tkapp' object has no attribute 'shared_Data'" when I run script. I am trying to implement what is described in this answer https://stackoverflow.com/a/33650527/7336464
import tkinter as tk
from tkinter import ttk
LARGE_FONT = ("Verdana", 20)
class APTInventoryapp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.iconbitmap(self, default='barcode_scanner.ico')
tk.Tk.title(self, 'Inventory Scanning')
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, PageOne):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky='nsew')
self.shared_Data = {'String1': tk.StringVar(),
'String2' : tk.StringVar(),
'number1' : tk.IntVar()
}
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
upcOutputLabel = ttk.Label(self, text = self.controller.shared_Data['String1'].get(), font=LARGE_FONT)
upcOutputLabel.pack(pady=10,padx=10)
upcEntryField = ttk.Entry(self)
upcEntryField.focus()
upcEntryField.pack()
upcEnterButton = ttk.Button(self, text = 'Enter', command=print('Enter Clicked!'))
upcEnterButton.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
mgrOutputLabel = ttk.Label(self, text = '', font=LARGE_FONT)
mgrOutputLabel.pack(pady=10,padx=10)
mgrbackButton = ttk.Button(self,text = 'Back to home', command=lambda: controller.show_frame(StartPage))
mgrbackButton.pack()
app = APTInventoryapp()
app.mainloop()
You don't create self.shared_Data until after you create the pages, but you are using it while creating the pages. There appears to be other errors in the app, but that's why you're getting this specific error.