Using the method suggested in Switch between two frames in tkinter I have tried to switch between a login frame and a register frame. Nevertheless, when showFrame(register) is called after the register button is pressed on the login screen, an attribute error occurs:
(frame=self.frames[pageName]; AttributeError:'loginScreen' object has no
attribute 'frames')
class mainActivity(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self,*args,**kwargs)
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 (loginScreen, register):
frame = F(container, self)
self.frames[F]=frame
frame.grid(row=0, column=0, sticky="snew")
self.showFrame(loginScreen)
def showFrame(self, pageName):
frame=self.frames[pageName]
frame.tkraise()
#-------------------------------------------------
class loginScreen(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller=controller
self.regBtn =Button(self, text="Register", command=self.regBtnClicked)
self.regBtn.grid(row=2,column=1)
self.grid()
def regBtnClicked(self):
mainActivity.showFrame(self, register)
#send to register screen
#-------------------------------------------------
class register(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller=controller
self.grid(row=0, column=0)
self.cancelBtn = Button(self, text="Cancel",command=self.cancelBtnClicked)
self.cancelBtn.grid(row=4, column=1)
def cancelBtnClicked(self):
#return to login screen
mainActivity.showFrame(self, loginScreen)
app = mainActivity()
app.mainloop()
There are several issues with your code. First you are missing an important potion of the for F in (loginScreen, register): for loop.
You need to have:
page_name = F.__name__
This is the line of code that is used to provide the string name for the key portion of the self.frames dictionary.
So instead of doing:
self.frames[F]=frame
Do this instead:
self.frames[page_name] = frame
Try to use the print statement while troubleshooting. All it took was for me to add print(self.frames) right before the error was occurring to have an idea of what is going wrong here.
You will also need to change self.showFrame(loginScreen) to self.showFrame("loginScreen") as well as any other call to showFrame() to reflect the same change.
Another issue you were having was this line:
mainActivity.showFrame(self, loginScreen)
You need to reference the controller instead here like this:
self.controller.showFrame("loginScreen")
You do not need self.grid(row=0, column=0) in your frame class's because these have already been set in your for loop. No need to do it again.
With all that take a look at the below code:
from tkinter import *
class mainActivity(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 (loginScreen, register):
page_name = F.__name__
frame = F(container, self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="snew")
print(self.frames)
self.showFrame("loginScreen")
def showFrame(self, pageName):
frame=self.frames[pageName]
frame.tkraise()
#-------------------------------------------------
class loginScreen(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller=controller
self.regBtn = Button(self, text="Register", command=self.regBtnClicked)
self.regBtn.grid(row=2,column=1)
def regBtnClicked(self):
self.controller.showFrame("register")
#-------------------------------------------------
class register(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller=controller
self.cancelBtn = Button(self, text="Cancel",command=self.cancelBtnClicked)
self.cancelBtn.grid(row=4, column=1)
def cancelBtnClicked(self):
self.controller.showFrame("loginScreen")
app = mainActivity()
app.mainloop()
Related
So I have my Tkinter application that consist of multiple frame
All these multiple frames contain the same basic structure of many buttons; the only difference is that the buttons have a different bg on each page.
In my actual project, these buttons contain so many options, and so having to write the same basic code each time for all pages makes my code look unnecessarily long.
So I'm thinking: Is there a way to put all these buttons into a dictionary or list, and pack them onto each separate frame? (Bear in mind the button will need to inherit the bg variable from the specific frame.)
I've created a minimal example to illustrate what I mean:
import tkinter as tk
from tkinter import *
listt = []
self = None
bg_colour_for_this_frame = None
button1 = Button(self,text="Button 1",bg=bg_colour_for_this_frame,fg='white')
button2 = Button(self,text="Button 2",bg=bg_colour_for_this_frame,fg='blue')
button3 = Button(self,text="Button 3",bg=bg_colour_for_this_frame,fg='orange')
listt.append(button1)
listt.append(button2)
listt.append(button3)
class Tkinter(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, SecondPage):
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()
frame.winfo_toplevel().geometry("860x864")
frame.configure(bg='#000000')
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
Button(self,text='SecondPage',command=lambda:controller.show_frame(SecondPage)).pack()
for s in listt:
s.pack()
class SecondPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
Button(self,text='StartPage',command=lambda:controller.show_frame(StartPage)).pack()
for s in listt:
s.pack()
app = Tkinter()
app.mainloop()
Or maybe, instead of having a list, use a dictionary:
listt = {'button1':'Button[root,text="Button 1",bg=bg_colour_for_this_frame,fg="white"]',
'button2':'Button[root,text="Button 2",bg=bg_colour_for_this_frame,fg="red"]',
'button3':'Button[root,text="Button 3",bg=bg_colour_for_this_frame,fg="blue"]',
}
I get the error:
s.pack()
AttributeError: 'str' object has no attribute 'pack'
Since you can't create the Buttons before the page they're on exists, It would be simpler to make a function and call it during the initialization of each of the page classes — like the make_buttons() shown below:
import tkinter as tk
from tkinter import *
# Button options for all pages.
BTN_OPTS = [dict(text="Button 1", fg='white'),
dict(text="Button 2", fg='blue'),
dict(text="Button 3", fg='orange')]
def make_buttons(parent, bg_colour):
return [Button(parent, bg=bg_colour, **opts) for opts in BTN_OPTS]
class Tkinter(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, SecondPage):
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()
frame.winfo_toplevel().geometry("860x864")
frame.configure(bg='#000000')
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
Button(self, text='SecondPage',
command=lambda: controller.show_frame(SecondPage)).pack()
for btn in make_buttons(self, 'Pink'):
btn.pack()
class SecondPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
Button(self, text='StartPage',
command=lambda: controller.show_frame(StartPage)).pack()
for btn in make_buttons(self, 'green'):
btn.pack()
app = Tkinter()
app.mainloop()
A more sophisticated and object-oriented approach would be to define a base class for all page classes that had a method in it something like the function above, and then derive the concrete subclasses from that allowing them just inherit the method. It also gets rid of the global data because the button options are now in a (base) class attribute.
Here's a runnable example of how it could be done that way. Note: it requires Python 3.6+ because it uses object.__init_subclass__() which was added in that version:
import tkinter as tk
class Tkinter(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, SecondPage):
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()
frame.winfo_toplevel().geometry("860x864")
frame.configure(bg='#000000')
class BasePage(tk.Frame):
# Button options common to all pages.
BTN_OPTS = [dict(text="Button 1", fg='white'),
dict(text="Button 2", fg='blue'),
dict(text="Button 3", fg='orange')]
#classmethod
def __init_subclass__(cls, /, bg_color, **kwargs):
super().__init_subclass__(**kwargs)
cls.bg_color = bg_color
def __init__(self, parent, controller, text, command):
super().__init__(parent)
tk.Button(self, text=text, command=command).pack() # Next page button.
for btn in (tk.Button(self, bg=self.bg_color, **opts) for opts in self.BTN_OPTS):
btn.pack()
class StartPage(BasePage, bg_color='pink'):
def __init__(self, parent, controller):
super().__init__(parent, controller, text='SecondPage',
command=lambda: controller.show_frame(SecondPage))
class SecondPage(BasePage, bg_color='green'):
def __init__(self, parent, controller):
super().__init__(parent, controller, text='StartPage',
command=lambda: controller.show_frame(StartPage))
app = Tkinter()
app.mainloop()
I'm trying to color a Tkinter frame (Toolbar), and for some reason it does not work. I have tried to add the background parameter in the Frame constructor, and this does not work.
In the code below, I added the background parameter in the constructors of all classes, and it doesn't work for the Toolbar class. For the other classes it does work. Can anyone tell me what I'm missing here?
Thank you in advance.
Code:
from tkinter import *
from App.Configuration import Configuration as cfg
class App(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
# Setup Menu
MainMenu(self)
# Setup Frame
container = Frame(self)
container.grid(row=1, column=0)
toolbar_frame = Toolbar(container, self)
toolbar_frame.grid(row=0, column=0)
self.frames = {}
for F in (StartPage, PageOne, PageTwo):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=2, column=0)
self.show_frame(StartPage)
def show_frame(self, context):
frame = self.frames[context]
frame.tkraise()
class StartPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent, bg="blue")
class Toolbar(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent, bg="blue")
home_button = Button(controller, text="one", command=doNothing, background=cfg.toolbar_button_background,
foreground=cfg.toolbar_button_enabled_text)
home_button.grid(row=0, column=0)
app = App()
app.mainloop()
I have created my own python script following the suggestion of this post, Switch between two frames in tkinter.
I have also followed this post, How to access variables from different classes in tkinter python 3.
However, I want to set or change the value of the shared value in one frame and then access it in another, when I switch between the frames.
class MainApplication(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.app = {
"foo": None
}
self.frames = {}
for F in (PageOne, PageTwo):
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("PageOne")
def show_frame(self, page_name, **kwargs):
# self.app["foo"] = kwargs.get("bar")
frame = self.frames[page_name]
frame.tkraise()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
button = tk.Button(self, text="Login", command=lambda: self.func())
button.pack()
def func(self):
# self.controller.show_frame("PageTwo", bar="something")
self.controller.show_frame("PageTwo")
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
print(self.controller.app["foo"])
I tried adding arguments to the show_frame() function that allows for switching between frames but the result in PageTwo is still None. I have commented out the lines, to show what I attempted.
It's important to understand that PageTwo.__init__ runs at the very start of the program, before the user has a chance to do anything. If you want to access app after switching, you're going to have to add code that runs after switching.
The following answer, linked to from where you copied your code, shows you one way to do that:
How would I make a method which is run every time a frame is shown in tkinter
When I try to execute my Tkinter application created in python, it is giving me a blank application window. No Buttons/Labels are displaying. What may be the Issue ?
Codes are as follows:
import tkinter as tk
from tkinter import ttk
LARGE_FONT=("Verdana", 18)
class VNMSapp(tk.Tk):
def __int__(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, AdminPage):
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()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(text="THIS IS HOME PAGE", font=LARGE_FONT)
label.pack()
btn1 = tk.Button(self, text="Enter ADMIN PAGE",
command=lambda: controller.show_frame(AdminPage))
btn1.pack()
class AdminPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(text="THIS IS ADMIN PAGE", font=LARGE_FONT)
label.pack()
btn1 = tk.Button(self, text="Enter HOME PAGE",
command=lambda: controller.show_frame(StartPage))
btn1.pack()
app = VNMSapp()
app.mainloop()
It is not giving me any error also.
There is a typo in the definition of the __init__() method for class VNMSapp:
def __int__(self, *args, **kwargs):
should be
def __init__(self, *args, **kwargs):
As a result your __init__() method is not being called, so your widgets are not created.
Once you correct that you will find an additional problem where you are using a list, but I think that you meant to use a dictionary:
self.frames= []
...
self.frames[F] = frame
the second line will fail raising a TypeError exception because list indices must be integers, which frame objects are not.
Fix that by initialising self.frames to an empty dict:
self.frames= {}
Here is an example of my code
import tkinter as tk
from tkinter import ttk
class gui_programming(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="left", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, Page1):
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()
##number 1
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
c = tk.Canvas(self, bg="red", width=75, height=100)
c.place(x=0, y=0)
butt0 = ttk.Button(self, text="Next.", command=lambda:controller.show_frame(Page1))
controller.bind("1", lambda x: controller.show_frame(StartPage))
controller.bind("2", lambda x: controller.show_frame(Page1))
controller.bind("3", lambda x: controller.show_frame(Page2))
class Page1(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
lambda x: gui_programming().geometry("75x100+10+10")
c = tk.Canvas(self, bg="blue", width=300, height=200)
c.place(x=0, y=0)
app = gui_programming()
app.geometry("75x100+10+10")
app.mainloop()
So, how should I go about changing the window that each class appears in from 75x100 to something else, is there a command to resize the window? I would like to be able to resize it for each class.
You already resized the app window from its default with
app.geometry("75x100+10+10")
Repeat this with a different size.
I recommend to always use variables in geometry
e.g
w=500
h=500
master.geometry(('{}x{}').format(w,h))
Call geometry inside __init__ method:
class gui_programming(tk.Tk):
def __init__(self, *args, **kwargs):
...
self.geometry("75x100+10+10")