Tkinter multiple frames/'pages': using canvas images - python-3.x

I would like to employ multiple frames in a GUI, where the page switches depending on the button clicked. I know that there's several threads already about this, and I've been looking at this one.
However, for my pages, I need different images on canvasses within each of my frames, so that when I raise a different frame, it comes with a new canvas and a new image on that canvas. I've tried a lot but I don't know how to get it to work so that the canvasses appear with their images.
Here's what I have so far, mostly copying from above link:
import tkinter as tk # python3
TITLE_FONT = ("Helvetica", 18, "bold")
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
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 = {}
self.frames["StartPage"] = StartPage(parent=container, controller=self)
self.frames["PageOne"] = PageOne(parent=container, controller=self)
self.frames["PageTwo"] = PageTwo(parent=container, controller=self)
self.frames["StartPage"].grid(row=0, column=0, sticky="nsew")
self.frames["PageOne"].grid(row=0, column=0, sticky="nsew")
self.frames["PageTwo"].grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self._canvas = tk.Canvas(parent, bg='white', width=900, height=3517, scrollregion=(0, 2800, 100, 800))
self._photo = tk.PhotoImage(file='images/homegraphic.gif')
self._canvas.create_image(0, 0, image=self._photo, anchor='nw')
label = tk.Label(self, text="This is the start page", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame("PageOne"))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame("PageTwo"))
button1.pack()
button2.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 1", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 2", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
How do I get the canvas image to appear? I've spent a long time trying to figure this out and would appreciate any help!

The problem is here:
self._canvas = tk.Canvas(parent, ...)
Everything within a page needs to be a child of the page or one of its descendants.
It needs to be this:
self._canvas = tk.Canvas(self, ...)

Related

Tkinter need to update a box based on whats typed in another box on another page

I have some pages in a container, and I want the name box that the user is allowed to type into from one of my pages to update and be displayed on the other page. This is not a variable in all my pages, this is only something I want displayed in 1 of my pages. My app has many pages, but this is my minimal reproducible example of my problem.
I know when my "template" page is created, the text in the name box is blank, so I need to somehow pass the variable when the 'load_page' function is called, but I cannot figure out how to make this work. Any help is appreciated.
The code below gives the error: AttributeError: type object 'template' has no attribute 'name_box' - The problem I have is I do not know how to specify the name box from one page to grab the entered text, and then insert it into another box in another page.
See code below:
import tkinter as tk
from tkinter import font as tkfont, filedialog, messagebox
class SLS_v1(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title('SLS')
self.geometry("552x700")
self.resizable(False, False)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
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 = {}
self.frames["MenuPage"] = MenuPage(parent=container, controller=self)
self.frames["template"] = template(parent=container, controller=self)
self.frames["MenuPage"].grid(row=0, column=0, sticky="nsew")
self.frames["template"].grid(row=0, column=0, sticky="nsew")
self.show_frame("MenuPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class MenuPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
name_label = tk.Label(self, text='Name:')
name_label.pack(pady=(20,0))
self.name_var = tk.StringVar()
self.name_entry = tk.Entry(self, width=10, textvariable=self.name_var)
self.name_entry.pack()
template = tk.Button(self, text='Template', height=3, width=20, bg='white', font=('12'),
command=lambda: self.load_page(controller))
template.pack(pady=50)
def load_page(self, controller):
controller.show_frame('template')
template.name_box.insert(tk.END, self.name_entry.var.get())
class template(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.grid(columnspan=10, rowspan=10)
top = tk.Label(self, height=3, width=80, bg='dark grey')
top.grid(row=0, column=0, columnspan=10)
self.back_btn = tk.Button(self, text='BACK', font=('Helvetica', '14'), bg='dark grey',
command=lambda: controller.show_frame('MenuPage'))
self.back_btn.grid(row=0, column=0, columnspan=2, padx=10, pady=10)
name_var = tk.StringVar()
name_box = tk.Entry(self, width=10, textvariable=name_var)
name_box.grid(row=1, column=1)
if __name__ == "__main__":
app = SLS_v1()
app.mainloop()
Edit: Fixed error in code.
You can solve this by importing GC and changing the function that is called when you click the template button to adding these few lines:
import gc
...
...
def load_page(self, controller):
controller.show_frame('template')
for obj in gc.get_objects():
if isinstance(obj, template):
obj.name_box.delete(0, tk.END)
obj.name_box.insert(tk.END, self.name_entry.get())

I want to make a GUI but i get this error: self.frame.grid(row=0, column=0, sticky="nsew") AttributeError: 'function' object has no attribute 'grid'

Sorry about the layout of this question, my first time working with stackoverflow posts.
I want to make a GUI for Tic Tac Toe (Game menu). And have the abillity to put buttons where ever i want in the GUI so i used Grid.
import tkinter as tk
LARGE_FONT= ("Verdana", 12)
class SeaofBTCapp(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 (MainWindow, Game, Difficulty):
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 MainWindow(tk.Tk):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Welcom to TIC TAC TOE", font=LARGE_FONT)
label.pack(pady=10, padx=10)
button1 = tk.Button(self, text="Start",
command=lambda: controller.show_frame(Game))
button1.pack()
button2 = tk.Button(self, text="Diffeculty",
command=lambda: controller.show_frame(Difficulty))
button2.pack()
button3 = tk.Button(self, text="Quit", command=self.Quit)
button3.pack()
label1 = tk.Label(self, text="Made by VindictaOG")
label1.pack()
def Quit(self):
exit()
class Game(tk.Tk):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
button1 = tk.Button(self, text="New Game")
button1.pack()
button2= tk.Button(self, text="Back to homescreen",
command=lambda: controller.show_frame(MainWindow))
button2.pack()
class Difficulty(tk.Tk):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
button1 = tk.Button(self, text="1V1", command=lambda: controller.show_frame(MainWindow))
button1.pack()
button2 = tk.Button(self, text="Back to homescreen",
command=lambda: controller.show_frame(Game))
button2.pack()
gui = SeaofBTCapp()
gui.mainloop()
But when i use grid i get this error:
Traceback (most recent call last):
File "/home/ivar/PycharmProjects/J1B2Afvink6/BKE.py", line 82, in <module>
gui = SeaofBTCapp()
File "/home/ivar/PycharmProjects/J1B2Afvink6/BKE.py", line 27, in __init__
frame.grid(row=0, column=0, sticky="nsew")
TypeError: wm_grid() got an unexpected keyword argument 'row'
I tried it with pack but that would not work, does someone know how to fix this?
Should inherit from tk.Frame instead of tk.Tk for MainWindow, Game and Difficulty. Also StartPage is not defined. #acw1668
Using (tk.Frame, tk.Tk) instead of just (tk.Tk) solved the problem.

Is there a way to change window title with multiple frames

I'm trying to create a window with multiple frames in tkinter. But the window title remains the same for any active frame.
The code is used for reference is here.
I tried to add
self.container.title(title)
in respective Pages classes. but the window get the title which was specified the latest.
'''
Solution from...
https://stackoverflow.com/questions/7546050/switch-between-two-frames-in-tkinter
'''
import tkinter as tk # python 3
from tkinter import font as tkfont # python 3
#import Tkinter as tk # python 2
#import tkFont as tkfont # python 2
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
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, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.controller.title("Start Page")
label = tk.Label(self, text="This is the start page", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame("PageOne"))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame("PageTwo"))
button1.pack()
button2.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.controller.title("Page One")
label = tk.Label(self, text="This is page 1", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.controller.title("Page Two")
label = tk.Label(self, text="This is page 2", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
Here I expected it to have title of the window to change whenever the frame changed but it prints the title as "Page Two" for entire time even I jump from one frame to another.
Yes, you can use the parameter page_name to change the title of the app when a frame changes
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
self.title(page_name) # update the app title

New Tk window opens when changing background color

I have a GUI that contains several frames, each containing multiple labels/entry fields. I am trying to add a "Settings" option that will allow the user to change the background color of all frames & labels. So far I have managed to accomplish the task however with a caveat of a new Tk window popping up with the selected background instead of updating on the current window.
import tkinter as tk
from tkinter import ttk
from tkinter import colorchooser
bg_hex = '#f0f0f0f0f0f0' #default background color
def pick_color():
global bg_hex
bg_color = colorchooser.askcolor()
bg_hex = bg_color[1]
Master().update()
print(bg_hex)
class Master(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 (HomePage, PageOne, PageTwo, Settings):
frame = F(container, self)
self.frames[F] = frame
frame.config(bg = bg_hex)
frame.grid(row=0, column=0, sticky='nsew')
self.show_frame(HomePage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class HomePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text='Home Page', font=('Verdana', 12), bg=bg_hex)
label.pack(pady=5)
button1 = tk.Button(self, text='Page One', command=lambda: controller.show_frame(PageOne))
button1.pack(pady=5, ipadx=2)
button2 = tk.Button(self, text='Page Two', command=lambda: controller.show_frame(PageTwo))
button2.pack(pady=5)
button3 = tk.Button(self, text='Settings', command=lambda: controller.show_frame(Settings))
button3.pack(side='bottom', pady=10)
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
tk.Label(self, text='Page One', font='Verdana 14 bold underline', bg=bg_hex).grid(row=0, columnspan=2, pady=5)
button1 = tk.Button(self, text='Back to Home', command=lambda: controller.show_frame(HomePage))
button1.grid()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
tk.Label(self, text='Page Two', font='Verdana 14 bold underline', bg=bg_hex).grid(row=0, columnspan=2,pady=5)
button1 = tk.Button(self, text='Back to Home', command=lambda: controller.show_frame(HomePage))
button1.grid()
class Settings(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
tk.Label(self, text='Settings', font='Verdana 14 bold underline', bg=bg_hex).grid(row=0, columnspan=2,pady=5)
button1 = tk.Button(self, text='Back to Home', command=lambda: controller.show_frame(HomePage))
button1.grid()
button2 = tk.Button(self, text='Choose Background', command= pick_color)
button2.grid()
Master().mainloop()
No errors occur when running the the block of code, but when you select the "Choose Background" button and pick a color, a new Tk window opens with the selected background color instead of updating the current Tk window.
**Updating code to reflect no global variables in hopes that it helps someone else down the road.
I added self.controller = controller under each Frame class, combined pick_color and color_update into 1 function and placed under the Tk class.
def pick_color_bg(self):
bg_color = colorchooser.askcolor()
bg_hex = bg_color[1]
# Loop through pages and contained widgets and set color
for cls, obj in self.frames.items():
obj.config(bg=bg_hex) # Set frame bg color
for widget in obj.winfo_children():
if '!label' in str(widget):
widget.config(bg=bg_hex) # Set label bg color
Lastly, change the Button command to command=self.controller.pick_color_bg.
With these changes I was able to eliminate the need for global variables.
In the function pick_color() you create a new instance of Tk() which gets the new color:
Master().update() # Creates a new root window!
To change the color of the existing root window you will have to save a reference to it. Also you will have to write a function in the class Master() which updates the bg color. The color does not update automatically when you call update(), you have to configure the bg color for each frame.
some more
I'm having trouble reading your code without rewriting it quite a bit. You use the name Master for the class that instantiates the root window. I would call it Application or similar as the name master usually means a master as in "master and server" or maybe "parent". Also you use the name frame, which usually is the name of a frame, as the name for the different page class instances (HomePage, ... etc). This makes it difficult to read. It's like the word blue written with red letters.
I would rewrite it with names that are more descriptive which makes it easier to grasp. Then the problems will be easier to find and correct.
and even more
Took me a while but here is an example of how it could work with minor changes:
import tkinter as tk
from tkinter import ttk
from tkinter import colorchooser
bg_hex = '#f0f0f0f0f0f0' #default background color
def pick_color():
global bg_hex
bg_color = colorchooser.askcolor()
bg_hex = bg_color[1]
root.update_color() # Call function for updating color
print(bg_hex)
class Master(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 (HomePage, PageOne, PageTwo, Settings):
frame = F(container, self)
self.frames[F] = frame
frame.config(bg = bg_hex)
frame.grid(row=0, column=0, sticky='nsew')
self.show_frame(HomePage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def update_color(self):
# Loop through pages and contained widgets and set color
for cls, obj in self.frames.items():
obj.config(bg=bg_hex) # Set frame bg color
for widget in obj.winfo_children():
if '!label' in str(widget):
widget.config(bg=bg_hex) # Set label bg color
class HomePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text='Home Page', font=('Verdana', 12), bg=bg_hex)
label.pack(pady=5)
button1 = tk.Button(self, text='Page One', command=lambda: controller.show_frame(PageOne))
button1.pack(pady=5, ipadx=2)
button2 = tk.Button(self, text='Page Two', command=lambda: controller.show_frame(PageTwo))
button2.pack(pady=5)
button3 = tk.Button(self, text='Settings', command=lambda: controller.show_frame(Settings))
button3.pack(side='bottom', pady=10)
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
tk.Label(self, text='Page One', font='Verdana 14 bold underline', bg=bg_hex).grid(row=0, columnspan=2, pady=5)
button1 = tk.Button(self, text='Back to Home', command=lambda: controller.show_frame(HomePage))
button1.grid()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
tk.Label(self, text='Page Two', font='Verdana 14 bold underline', bg=bg_hex).grid(row=0, columnspan=2,pady=5)
button1 = tk.Button(self, text='Back to Home', command=lambda: controller.show_frame(HomePage))
button1.grid()
class Settings(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
tk.Label(self, text='Settings', font='Verdana 14 bold underline', bg=bg_hex).grid(row=0, columnspan=2,pady=5)
button1 = tk.Button(self, text='Back to Home', command=lambda: controller.show_frame(HomePage))
button1.grid()
button2 = tk.Button(self, text='Choose Background', command= pick_color)
button2.grid()
root = Master() # Save a reference to the root window
root.mainloop()
I would recommend against changing color by means of a function in global scope. I think it would be better placed as a function of the Master() class. Then you would not have to use global variables.

pass entry value from one class to another class tkinter python

I want to pass entry values from one class to another using the get() method and i dont know how. Thanks in advance
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
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.x=tk.StringVar()
self.y=tk.StringVar()
self.key=tk.IntVar()
self.key.set(0)
self.frames = {}
for F in (StartPage, PageOne, PageTwo, LoggedIn, Balance):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="Banking Application", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="User Login",
command=lambda: controller.show_frame("PageOne"))
button2 = tk.Button(self, text="Create Account",
command=lambda: controller.show_frame("PageTwo"))
button1.pack()
button2.pack()
This is from where i want to pass the userid entry value to Balance class
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="Login Page", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
#user id entry block
tk.Label(self,text="UserID").pack(side="top", fill="x", pady=4)
self.userid=tk.Entry(self,textvariable=self.controller.x)
self.userid.pack(side="top", fill="x", pady=6)
#password entry block
tk.Label(self,text="Password").pack(side="top", fill="x", pady=4)
self.password=tk.Entry(self,textvariable=self.controller.y)
self.password.pack(side="top", fill="x", pady=6)
self.submit=tk.Button(self, text="SUBMIT",
command=self.login)
self.submit.pack();
button = tk.Button(self, text="Back to Main Menu",
command=lambda: controller.show_frame("StartPage"))
button.pack()
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
def getname(self):
name=self.controller.x.get()
print (name)
return name
def login(self):
getname(self);
for x in range(0,listsize):
if(self.controller.x.get()==files[x]):
print("welcome %s"%(self.controller.x.get()))
if(self.controller.y.get()==keys[x]):
bal=open(self.controller.x.get()+'.txt','r')
currbal=int(bal.read())
print("login success")
self.controller.key.set(1)
# print(self.controller.key=1)
self.controller.show_frame("LoggedIn");
break
else:
print("Invalid credentials")
How to pass userid from pageone to this class
class Balance(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="Balance Enquiry", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
username = PageOne.getname(self);
print(username)
if(self.controller.key==1):
print(username)
bal=open(username+'.txt','r')
currbal=int(bal.read())
label1 = tk.Label(self, text=currbal)
label1.pack(side="top", fill="x", pady=10)
###########
button = tk.Button(self, text="Go to the savings page",
command=lambda: controller.show_frame("LoggedIn"))
button.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()

Resources