Related
I am a newbie in Python, I am keen in being able to swap between different frame in Tkinter. But I can't seems to be able to do it with Canvas. Any expert able to help me point out my mistake?
My main goal is to swap effectively between StartPage and PageOne.
import tkinter as tk
class backbone(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self,*args, **kwargs)
container=tk.Frame(self)
container.config(width=600,height=400,bg="beige")
container.pack()
self.frames={}
for F in (StartPage, PageOne):
frame=F(container,self)
self.frames[F]=frame
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)
canvas = tk.Canvas(self, width = 600, height = 400, bg='#aaaaff')
canvas.pack()
btn_2=tk.Button(self, text="Start Page", bg='#3ab54a',fg='blue',
command=lambda: controller.show_frame(PageOne))
btn_2.place(relx=0.35, rely=0.79, relwidth=0.3, relheight=0.1)
btn_2.pack()
class PageOne(tk.Frame):
def __init__(self,parent,controller):
tk.Canvas.__init__(self,parent)
canvas = tk.Canvas(self, width = 600, height = 400, bg='#aaaaff')
canvas.pack()
btn_1=tk.Button(self, text="PageOne", bg='#3ab54a',fg='blue',
command=lambda: controller.show_frame(StartPage))
btn_1.place(relx=0.35, rely=0.79, relwidth=0.3, relheight=0.1)
btn_1.pack()
app=backbone()
app.mainloop()
Here ya go. You had a bunch of issues. I commented everything in the code that was fixed or changed.
import tkinter as tk
#prepare some data
ButtonPlace = dict(relx=0.35, rely=0.79, relwidth=0.3, relheight=0.1)
ButtonConfig = dict(bg='#3ab54a', fg='blue', activebackground='#3ab54a', activeforeground='blue')
CanvasConfig = dict(width=600, height=400, highlightthickness=0)
#class names should start with a capital letter
class BackBone(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
#you don't need an inner frame so I got rid of it
#init some vars for storing and managing pages
self.page = 0
self.pages = {}
self.page_names = []
#make a refererence of pages and page names
for C in [StartPage, PageOne]:
self.pages[C.NAME]=C(self)
self.page_names.append(C.NAME)
#you can just use one button for every page
self.btn = tk.Button(self, text="Start Page", command=self.next_page, **ButtonConfig)
self.btn.place(**ButtonPlace)
#init start page
self.btn.invoke()
def next_page(self):
#whatever page is packed ~ forget it
for n, f in self.pages.items():
f.pack_forget()
#get page name
name = self.page_names[self.page]
#pack page associated with name
self.pages[name].pack()
#change button text to the name of this page (same as you had it)
self.btn['text'] = name
#raise the button up in z-order
self.btn.tkraise()
#prime next page number
self.page = (self.page + 1) % len(self.page_names)
class StartPage(tk.Canvas):
#static page name reference
NAME = 'Start Page'
#the proper term is master ~ not parent. controller is no more
def __init__(self, master):
tk.Canvas.__init__(self, master, bg='#aaaaff', **CanvasConfig)
#you don't need a frame. make the whole thing a canvas
#apply StartPage comments to this page, as well
class PageOne(tk.Canvas):
NAME = 'Page One'
def __init__(self, master):
tk.Canvas.__init__(self, master, bg='#ffaaaa', **CanvasConfig)
#this is the proper way to initialize your app
if __name__ == '__main__':
app = BackBone()
app.configure(bg='beige', highlightthickness=0, bd=0)
app.resizable(False, False)
app.mainloop()
I understand how to bind keys when it's just on a simple frame but since I built my app in a different way, I can't seem to figure out how to bind the return key to press the button or run the function that the button is bounded to. I've been searching for a similar question by others on the website but I haven't found one similar to mine.
I've toned down the rest of my code and have it below:
import tkinter as tk
from tkinter import *
class POS(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 (ErrorPage, MainPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column = 0, sticky = "nsew")
self.show_frame(MainPage)
def show_frame(self,cont):
frame = self.frames[cont]
frame.tkraise()
class MainPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
frame = tk.Frame(self)
frame.pack(fill = BOTH)
button = Button(frame, text = "OK", command = self.bindHello)
button.pack(pady=5, padx=10)
frame.bind("<Return>", self.bindHello)
self.bind("<Return>", self.bindHello)
def bindHello(self, event=None):
print("HELLO1")
#Yes this doesn't do anything but I need it for the frame container as set before
class ErrorPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
frame = tk.Frame(self)
frame.pack(fill = BOTH)
button = Button(frame, text = "OK", command = self.bindHello)
button.pack(pady=5, padx=10)
frame.bind("<Return>", self.bindHello)
def bindHello(self, event=None):
print("HELLO2")
app = POS()
app.mainloop()
Simple Button bind I had intended to work as is as follows:
from tkinter import *
master = Tk()
def callback(event=None):
print("Hello " + entry.get())
entry = StringVar()
e = Entry(master, textvariable = entry, width = 15)
e.pack()
b = Button(master, text="OK", command = callback)
b.pack()
master.bind("<Return>", callback)
mainloop()
I would just like to have a simple button bind like the one above but I can't seem to find a way for my main program to work. I think it's due to the way I structured my app, but I'm not entirely sure.
On your sample you bind to the window itself. You can do so in the other one as well in a number of ways:
#1 bind to page object's direct parent, which happens to be a Toplevel-like
#self.master.bind('<Return>', self.bindHello)
#2 recursively search page object's parents, and bind when it's a Toplevel-like
#self.winfo_toplevel().bind('<Return>', self.bindHello)
#3 bind to page object's inner frame's parent's parent, which happens to be a Toplevel-like
#frame.master.master.bind('<Return>', self.bindHello)
#4 recursively search page object's inner frame's parents, and bind when it's a Toplevel-like
frame.winfo_toplevel().bind('<Return>', self.bindHello)
I am writing a program that will take skill names as input from text entries and calculate the corresponding value of all of the skills entered. When I enter a skill in the program and then print the skill to the shell it appears as an object? Why does this happen and how can I fix it, do I need a repr or str? Why doesn't the delete method to clear the text entry work as well?
import tkinter as tk
from tkinter import ttk
#make the lists to store the skill names
floorEle1Skills = []
class startValue(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "Start Value Calculator")
tk.Tk.minsize(self, width = 350, height = 300)
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, floorPage, pommelPage, ringsPage, vaultPage, pbarsPage, hbarPage):
frame = f(container, self)
self.frames[f] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
self.showFrame(startPage)
#make the lists to store the skill names
floorEle1Skills = []
def showFrame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def floorEle1(skill):
floorEle1Skills.append(skill)
#clear the text entry
#ele1Entry.delete(0, tk.END)
#why doesnt this work???
#why is it printed as an object??
print(floorEle1Skills)
class startPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text = "Select Event")
label.pack(pady = 10, padx = 10)
floorButton = ttk.Button(self, text = "Floor", command = lambda : controller.showFrame(floorPage))
floorButton.pack()
class floorPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text = "Floor")
label.pack(pady = 10, padx = 10)
#make the entries and labels
ele1Label = tk.Label(self, text = "Element Group 1:")
ele1Label.pack()
skill1 = tk.StringVar()
ele1Entry = tk.Entry(self, textvariable = skill1)
ele1Entry.pack()
ele1Button = ttk.Button(self, text = "Add", command = lambda : controller.floorEle1())
ele1Button.pack()
startButton = ttk.Button(self, text = "Back to Start", command = lambda : controller.showFrame(startPage))
startButton.pack(side = 'bottom')
Welcome to Python. The problem is in the function floorEle1(skill). This is a member function of class startValue, but the argument list doesn't begin with self. Python doesn't force you to name the first variable self; you can actually name it whatever you want (but don't do it!). So within this function the variable named skill acts just like the variable self.
It's exactly as if you had written this:
def floorEle1(self):
floorEle1Skills.append(self)
#clear the text entry
#ele1Entry.delete(0, tk.END)
#why doesnt this work???
#why is it printed as an object??
print(floorEle1Skills)
I think you can see now that your code, in effect, appends self to floorEle1Skills; i.e., you append the instance of your main window! So when you print the list, the print statement shows that the list contains an object.
As already mentioned in the another answer the problem with the code turns around the function floorEle1(self, skill), BUT ... there are also some other issues that should be properly addressed in order to get the entered skills passed to the list of skills (see code below):
import tkinter as tk
from tkinter import ttk
#make the lists to store the skill names
# floorEle1Skills = []
class startValue(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "Start Value Calculator")
tk.Tk.minsize(self, width = 350, height = 300)
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, floorPage): # , pommelPage, ringsPage, vaultPage, pbarsPage, hbarPage):
frame = f(container, self)
self.frames[f] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
self.showFrame(startPage)
#make the lists to store the skill names
self.floorEle1Skills = []
def showFrame(self, cont):
self.floorEle1Skills = []
frame = self.frames[cont]
frame.tkraise()
def floorEle1(self, skill):
print("#", skill.get())
self.floorEle1Skills.append(skill)
#clear the text entry
#ele1Entry.delete(0, tk.END)
#why doesnt this work???
#why is it printed as an object??
for item in self.floorEle1Skills:
print("##",item.get())
class startPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text = "Select Event")
label.pack(pady = 10, padx = 10)
floorButton = ttk.Button(self, text = "Floor", command = lambda : controller.showFrame(floorPage))
floorButton.pack()
class floorPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text = "Floor")
label.pack(pady = 10, padx = 10)
#make the entries and labels
ele1Label = tk.Label(self, text = "Element Group 1:")
ele1Label.pack()
skill1 = tk.StringVar()
ele1Entry = tk.Entry(self, textvariable = skill1)
ele1Entry.pack()
ele1Button = ttk.Button(self, text = "Add", command = lambda : controller.floorEle1(ele1Entry))
ele1Button.pack()
startButton = ttk.Button(self, text = "Back to Start", command = lambda : controller.showFrame(startPage))
startButton.pack(side = 'bottom')
root = tk.Tk()
my_gui = startValue()
root.mainloop()
Other changes in the code are:
definition of self.floorEle1Skills = [] in the '__ init __()' function and passing the appropriate parameter to controller.floorEle1(ele1Entry) so that the input string value is passed to the function handling the button push.
The above code prints the user input to the terminal (twice, first from the passed user input, second all items in the list).
Placing self.floorEle1Skills = [] line in showFrame() resets the list collecting the input of skills (making restart of input possible).
The code above solves both issues addressed in the question, but this doesn't mean that there are not further issues needing to be solved.
I'm having some trouble calling a module(updateUI) within a class (Eventsim).
The line Sim = EventSim() throws an exception because it's missing an argument (parent). I can't figure out how to fix this / reference the parent object.
This is my first attempt wit Tkinter and my python knowledge is also rather limited (for now).
from Tkinter import *
class EventSim(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
def updateUI(self,IP_Address,Port_Number,Events_Directory):
self.parent.title('ECP Event Simulator')
self.parent.resizable(0, 0)
self.pack(fill=BOTH, expand=True)
frame1 = Frame(self)
frame1.pack(fill=X)
frame2 = Frame(self)
frame2.pack(fill=X)
frame3 = Frame(self)
frame3.pack(fill=X)
frame4 = Frame(self)
frame4.pack(fill=X)
frame5 = Frame(self)
frame5.pack(fill=X)
frame6 = Frame(self)
frame6.pack(fill=X,pady=(10,30))
frame7 = Frame(self)
frame7.pack(fill=X)
frame8 = Frame(self)
frame8.pack(fill=X,pady=(10,0))
Main_Label = Label(frame1,text='ECP EventSim')
Main_Label.pack(side=LEFT,padx=100)
IP_Label = Label(frame2,text='IP Address:')
IP_Label.pack(side=LEFT,padx=10)
Port_Label = Label(frame2,text='Port:')
Port_Label.pack(side=RIGHT,padx=70)
IP_Text = Entry(frame3)
IP_Text.pack(fill=X,side=LEFT,padx=10)
IP_Text = Entry(frame3)
IP_Text.pack(fill=X,side=RIGHT,padx=10)
Dir_Label = Label(frame4,text='Events Directory:')
Dir_Label.pack(side=LEFT,padx=10)
Dir_Text = Entry(frame5)
Dir_Text.pack(fill=X,side=LEFT,padx=10,expand=True)
Save_Button = Button(frame6,text='Save Config')
Save_Button.pack(fill=X,side=LEFT,padx=10,expand=True)
Con_Button = Button(frame7,text='Connect')
Con_Button.pack(fill=X,side=LEFT,padx=10,expand=True)
Send_Button = Button(frame8,text='Start Sending Events')
Send_Button.pack(fill=X,side=LEFT,padx=10,expand=True)
def main():
root = Tk()
root.geometry("300x300+750+300")
app = EventSim(root)
root.mainloop()
Sim = EventSim()
Sim.updateUI('1','1','1')
main()
The parent should be root. So, replacing:
def main():
root = Tk()
root.geometry("300x300+750+300")
app = EventSim(root)
root.mainloop()
Sim = EventSim()
Sim.updateUI('1','1','1')
main()
with:
root = Tk()
root.geometry("300x300+750+300")
Sim = EventSim(root)
Sim.updateUI('1','1','1')
root.mainloop()
brings up the desired window. The updateUI method requires work to populate the entry fields but you can remove its parent parameter since you already have the parent instance variable.
Remove Sim = EventSim() and move Sim.updateUI('1','1','1') to main:
def main():
root = Tk()
root.geometry("300x300+750+300")
app = EventSim(root)
app.updateUI('1','1','1')
root.mainloop()
main()
I'm trying a simple experiment, I have 3 frames, frame 1 has two labels - "for page 2" and "for page 3", it also has 2 radio buttons corresponding to the labels. based on which radio button is selected, when the user hits the next page button, I want the button to bring the user to the selected page
this is the code -
import Tkinter as tk
class MainApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# the main container that holds all the frames
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 = {}
# adding frames to the dictionary
for F in (Page1,Page2,Page3):
frame = F(container,self)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "w")
self.show_frame(Page1)
def show_frame(self,page_name):
#SHOWS A FRAME WITH THE GIVEN NAME
for frame in self.frames.values():
frame.grid_remove()
frame = self.frames[page_name]
frame.grid()
#STACKING THE FRAMES
#frame = self.frames[cont]
#frame.tkraise()
class Page1(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
lbl1 = tk.Label(self,text = "for page 2",font =("Helvetica",12,"bold"))
lbl1.grid(row=1,sticky="W")
lbl2 = tk.Label(self,text = "for page 3",font =("Helvetica",12,"bold"))
lbl2.grid(row=1,column=1,sticky="W")
btn1 = tk.Button(self, text="next page", font=('MS', 24, 'bold'))
btn1.grid(row=3,column = 0,columnspan=1)
#btn1['command'] = lambda: controller.show_frame(Page2)
self.var1 = tk.BooleanVar()
rButton1 = tk.Radiobutton(self,variable = self.var1,value=True)
rButton1.grid(row=2,sticky = "W")
rButton2 = tk.Radiobutton(self,variable = self.var1,value=False)
rButton2.grid(row=2,column=1,sticky = "W")
if self.var1.get() == 1:
btn1['command'] = lambda: controller.show_frame(Page3)
if self.var1.get() == 0:
btn1['command'] = lambda: controller.show_frame(Page2)
class Page2(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
lbl = tk.Label(self,text="This is page 2",font=("Helvetica",12,"bold"))
lbl.pack()
class Page3(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
lbl = tk.Label(self,text="This is page 3",font=("Helvetica",12,"bold"))
lbl.pack()
app = MainApp()
app.mainloop()
I assumed that by using a few basic conditions (located in my PageOne class) -
self.var1 = tk.BooleanVar()
rButton1 = tk.Radiobutton(self,variable = self.var1,value=True)
rButton1.grid(row=2,sticky = "W")
rButton2 = tk.Radiobutton(self,variable = self.var1,value=False)
rButton2.grid(row=2,column=1,sticky = "W")
if self.var1.get() == 1:
btn1['command'] = lambda: controller.show_frame(Page3)
if self.var1.get() == 0:
btn1['command'] = lambda: controller.show_frame(Page2)
I would be able to achieve this, but it doesn't seem to work. The conditions in my if statements are integers but to my knowledge 1 represents True and 0; False anyway? what am i doing wrong?
I think this is what you want. I didn't handle making sure the radiobutton isn't selected by default. I left that as an exercise to you. Although, if you're wanting to just switch pages like this I'd just use buttons (tk/ttk.Button), then you don't have to worry about handling the radiobutton. Although, that's just my preference either will work fine of course. You can just bind each button to switch the page. I commented the buttons out in your modified code below.
If you're wanting to create buttons / radiobuttons to have a forward / back option for each page. You can just iterate over the controllers frames to see which is the current, and create two buttons similar to the ones below to move to the other frames.
import tkinter as tk
class MainApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# the main container that holds all the frames
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 = {}
# adding frames to the dictionary
for F in (Page1,Page2,Page3):
frame = F(container,self)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "w")
self.show_frame(Page1)
def show_frame(self,page_name):
#SHOWS A FRAME WITH THE GIVEN NAME
for frame in self.frames.values():
frame.grid_remove()
frame = self.frames[page_name]
frame.grid()
#STACKING THE FRAMES
#frame = self.frames[cont]
#frame.tkraise()
class Page1(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
self.controller = controller
lbl1 = tk.Label(self,text = "for page 2",font =("Helvetica",12,"bold"))
lbl1.grid(row=1,sticky="W")
lbl2 = tk.Label(self,text = "for page 3",font =("Helvetica",12,"bold"))
lbl2.grid(row=1,column=1,sticky="W")
btn1 = tk.Button(self, text="next page", font=('MS', 24, 'bold'))
btn1.grid(row=3,column = 0,columnspan=1)
#btn1['command'] = lambda: controller.show_frame(Page2)
self.var1 = tk.BooleanVar()
#rButton1 = tk.Button(self, text='Show Page 2', command=lambda: self.controller.show_frame(Page2))
#rButton1.grid(row=2, sticky="W")
#rButton2 = tk.Button(self, text='Show Page 3', command=lambda: self.controller.show_frame(Page3))
#rButton2.grid(row=2, column=1, sticky="W")
rButton1 = tk.Radiobutton(self,variable = self.var1,value=True,
command=self.switch_pages)
rButton1.grid(row=2,sticky = "W")
rButton2 = tk.Radiobutton(self,variable = self.var1,value=False,
command=self.switch_pages)
rButton2.grid(row=2,column=1,sticky = "W")
def switch_pages(self):
if not self.var1.get():
self.controller.show_frame(Page3)
else:
self.controller.show_frame(Page2)
class Page2(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
lbl = tk.Label(self,text="This is page 2",font=("Helvetica",12,"bold"))
lbl.pack()
class Page3(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
lbl = tk.Label(self,text="This is page 3",font=("Helvetica",12,"bold"))
lbl.pack()
app = MainApp()
app.mainloop()