how to pass variables through pages using tktinter in python [duplicate] - python

I have been searching a lot and I still don't know how you access variables from different classes in python. In this case I want to access the variable self.v from PageOne class to PageTwo class.
Here is my code.
import tkinter as tk
import smtplib
TITLE_FONT = ("Helvetica", 18, "bold")
class SampleApp(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 (StartPage, PageOne, PageTwo):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, c):
frame = self.frames[c]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="PyMail",foreground = "Red", font=("Courier", 30, "bold"))
label.pack(side="top")
sublabel = tk.Label(self, text="Bringing you the\n the easiest way of communication",
font=("Courier", 15))
sublabel.pack()
wallpaper = tk.PhotoImage(file='Python-logo-notext.gif')
img = tk.Label(self, image=wallpaper)
img.image = wallpaper
img.pack(side="top", expand = True)
button1 = tk.Button(self, text="Click Here to Login to your account",fg="red",
command=lambda: controller.show_frame(PageOne))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame(PageTwo))
button2.pack(side="bottom")
button1.pack(side="bottom")
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller=controller
label = tk.Label(self, text="Personal Information", font=TITLE_FONT, foreground="blue")
label.pack(side="top", fill="x", pady=10)
global optionv
self.optionv = tk.StringVar()
self.optionv.set("---Select One---")
optionm = tk.OptionMenu(self, self.optionv, "---Select One---", "#gmail.com", "#yahoo.com", "#hotmail.com")
t1 = tk.Label(self, text="Email Account: ")
self.v = tk.StringVar()
self.v.set("")
entry1 = tk.Entry(self, textvariable=self.v)
t2 = tk.Label(self,text="\nPassword: ")
self.pwd = tk.StringVar()
self.pwd.set("")
entry2 = tk.Entry(self, textvariable=self.pwd)
entry2.config(show="*")
lgbutton=tk.Button(self, text="Log In", command=self.login)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame(StartPage))
#final = tk.Label(self, textvariable=self.v)
#finalpwd = tk.Label(self, textvariable=self.pwd)
t1.pack()
entry1.pack()
optionm.pack()
t2.pack()
entry2.pack()
#final.pack()
#finalpwd.pack()
lgbutton.pack()
button.pack(side="bottom")
def login(self):
value = tk.Label(self, text="Invalid username / password", font=("Courier", 15, "bold"), foreground="red")
success = tk.Label(self, text="Login was Successful \n (Click ""Continue"" to compose email)", font=("Courier", 15, "bold"), foreground="blue")
cbutton = tk.Button(self, text="Continue", command=lambda: self.controller.show_frame(PageTwo))
status = tk.Label(self, text="Please select your email domain", foreground="red")
if self.optionv.get() == "#gmail.com":
try:
global server
server = smtplib.SMTP("smtp.gmail.com", 587)
server.ehlo()
server.starttls()
server.login(self.v.get()+self.optionv.get(), self.pwd.get())
success.pack()
cbutton.pack(side="bottom")
except:
value.pack()
elif self.optionv.get() == "#yahoo.com":
try:
server = smtplib.SMTP("smtp.yahoo.com", 587)
server.ehlo()
server.starttls()
server.login(self.v.get()+self.optionv.get(), self.pwd.get())
success.pack()
cbutton.pack(side="bottom")
except:
value.pack()
elif self.optionv.get() == "#hotmail.com":
try:
server = smtplib.SMTP("smtp.live.com", 587)
server.ehlo()
server.starttls()
server.login(self.v.get()+self.optionv.get(), self.pwd.get())
success.pack()
cbutton.pack(side="bottom")
except:
value.pack()
else:
status.pack()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Compose Mail", font=TITLE_FONT, foreground="green")
label.pack(side="top", fill="x", pady=10)
self.reciever = tk.StringVar()
self.reciever.set("")
senderl = tk.Label(self, text="Send to: ")
rmail = tk.Entry(self, textvariable=self.reciever)
self.senderoption = tk.StringVar()
self.senderoption.set("---Select One---")
senderdomain = tk.OptionMenu(self, self.senderoption, "---Select One---", "#gmail.com", "#hotmail.com", "#yahoo.com")
self.mail = tk.StringVar()
self.mail.set("")
self.textw = tk.Entry(self, textvariable=self.mail)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame(StartPage))
sendbutton = tk.Button(self, text = "Send Mail", command=self.sendmail)
senderl.pack(side="top", anchor="w")
rmail.pack(side="top", anchor="nw")
senderdomain.pack(side="top", anchor="nw")
self.textw.pack(fill="both")
button.pack(side="bottom")
sendbutton.pack(side="bottom")
def sendmail(self):
sent = tk.Label(self, text="Email has been sent")
if self.senderoption.get() == "#gmail.com":
try:
server.sendmail(self.v.get()+self.optionv.get(), self.reciever.get()+self.senderoption.get(), "YES")
print("Success")
sent.pack()
except:
print("Unsuccesful")
print(PageOne.self.v.get())
if __name__ == "__main__":
app = SampleApp()
app.title("PyMail")
app.geometry("400x400")
app.mainloop()

At its core, your question has a simple answer. "How do I get a value from object X?" The answer is the same for any object: you get it by asking object X. All you need in order to do that is get a reference to the object and then access the attribute directly.
Accessing data from other pages
In your case, the code in PageTwo needs a reference to PageOne so you can get the v variable.
So, how do you get a reference? The code (which you copied either from a tutorial, or from the stackoverflow answer that the tutorial copied from) was designed to make this easy. Each page is given a reference to a controller, and this controller has a reference to each page. You can therefore ask the controller to give you a reference to a page.
The first step is to save the reference to the controller in each class. Interestingly, you're already doing this in PageOne, but you should do it in all the pages. Make sure you add self.controller = controller in every __init__ method, like so:
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
...
self.controller=controller
...
Next, we need to add a method in the controller class that will return a reference to the page. Add the following function to SampleApp:
class SampleApp(tk.Tk):
...
def get_page(self, page_class):
return self.frames[page_class]
...
Now, from within any "page" you can get access to the object for any other "page". For example, in PageTwo you can access the v variable from PageOne like this:
page1 = self.controller.get_page(PageOne)
page1.v.set("Hello, world")
Using shared data
An even better solution is for your SampleApp class to create a single set of variables that all of the pages share. You can create a dictionary in that class, and then use the controller to give every page access. For example:
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.shared_data = {
"username": tk.StringVar(),
"password": tk.StringVar(),
...
)
Then, from within any class you can access the data like this:
entry1 = tk.Entry(self, textvariable=self.controller.shared_data["username"])
...
username = self.controller.shared_data["username"].get()
The reason this is the better solution is that your pages don't have to know how the other pages are implemented. When a page relies on the exact implementation of another page this is called tight coupling. If the pages don't need to know how the other pages are implemented, this is called loose coupling.
Loose coupling gives you more flexibility. Instead of having every page tightly coupled to every other page, they are all tightly coupled to a single object: the controller. As long as every page knows only about the controller, each page is free to be changed at any time without affecting the rest of the program.
Of course, if you change the controller you have to change all of the pages, but if you do a good job designing the controller that's less likely to occur and easier to manage when it does occur.

This has to do with the global frame.
If you create a variable inside of a class, it will only exist inside of that function. If you want to 'transfer' a variable inside of a class (or function, for that matter) to the global frame, you use global.
class firstClass():
global my_var_first
my_var_first = "first variable"
print(my_var_first) # This will work, because the var is in the global frame
class secondClass():
my_var_second = "second variable"
print(my_var_first) # This will work, as the var is in the global frame and not defined in the class
print(my_var_second) # This won't work, because there is no my_var_second in the global frame
To visualise the memory, you can use pythontutor, as it will show you step by step how the memory is created.
I hope I could help you!
EDIT
I think I should add that if you define a variable inside a class/function with the same name as a variable in the global frame, it will not remove the global variable. Instead, it will create a new one (with the same name) in its own frame. A class or function will always use the variable in its own frame if available.
x = 5
def print_variable():
x = 3
print(x)
print(x)
print_variable()
# OUTPUT:
# 5
# 3

Related

Cannot Update tkinter Frame or Label using Instance method

I'm having an update problem in tkinter GUI, for past two days, I have searched a lot, Cant find something specific to my problem.This post Tkinter updating labels in stacked frame windows come close to my problem but not exactly. I am using classes to structure my application... The structure is given here Application structure image ( SOF not letting me embed images but link is provided )
From above structure you can see, I'm trying to make changes in DetailFrame from ListProduct Frame, now the code is reaching there and changing the values successfully but not updating the label, I'm using config method to change label... and frame background,but no luck..
I have tried StringVar as well for updating label, but nothing... Sample Code is provided below...
This application is a part of main app and for Original Code Structure Thanks to .. Bryan Oakley
class ProductWindow(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.geometry("600x500")
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.notebook = ttk.Notebook(container)
self.notebook.grid(row=0, column=0, sticky='nsew')
self.new_product_tab = tk.Frame(self.notebook, bg='#233223' )
self.list_product_tab = tk.Frame(self.notebook, bg='#323232')
self.edit_product_tab = tk.Frame(self.notebook, bg='#433434')
# Adding Tabs to Notebook
self.notebook.add(self.new_product_tab, text=" Add New Product ")
self.notebook.add(self.list_product_tab, text=" List All Product ")
self.notebook.add(self.edit_product_tab, text=" Edit Product ")
self.productframe = EditProductFrame(self.edit_product_tab)
self.detailframe = DetailFrame(self.productframe)
button = tk.Button(self.list_product_tab, text="Change background in Edit Form", command=self.change_method)
button.pack()
def change_method(self):
print("Trying to change the frame")
self.productframe.raise_edit_frame(DetailFrame)
self.detailframe.change_bg('green')
self.notebook.select(self.edit_product_tab)
if __name__ == "__main__":
testObj = ProductWindow()
testObj.mainloop()
In another file, I have DetailFrame below.
class EditProductFrame(tk.Frame):
def __init__(self, parent):
print("Edit product frame constructor is called...")
tk.Frame.__init__(self, parent)
self.pack(side='top', fill='both', expand=True)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
# define frames and pack them in
self.frames = {}
for F in {DetailFrame, EditFrame}:
frame = F(self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky='nsew')
self.raise_edit_frame(DetailFrame)
def raise_edit_frame(self, container):
frame = self.frames[container]
frame.tkraise()
class EditFrame(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.config(bg='green')
label = tk.Label(self, text="Edit Page",)
label.pack(pady=0,padx=0)
tk.Button(self, text="Go to Detail", command=lambda:parent.raise_edit_frame(DetailFrame)).pack()
class DetailFrame(tk.Frame):
def __init__(self, parent):
print("something detail view")
tk.Frame.__init__(self, parent)
self.config(bg='blue')
self.label = tk.Label(self, text='Original Label')
self.label.pack(pady=0,padx=0)
tk.Button(self, text="Go to Edit Frame", command=lambda:parent.raise_edit_frame(EditFrame)).pack()
def change_bg(self, color):
# doesn't update the background
self.config(bg=color)
# doesn't update the Label text
self.label.config(text='Changed Label')
# print the correct changed value = 'Changed Label'
print(self.label.cget('text'))
Thanks ...
Note that you have created another instance of DetailFrame (self.detailframe) inside ProductWindow but it is not visible since no layout function is called on it. Actually there is already an instance of DetailFrame created when creating instance of EditProductFrame, so you need to call change_bg() on this instance instead:
class ProductWindow(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.geometry("600x500")
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.notebook = ttk.Notebook(container)
self.notebook.grid(row=0, column=0, sticky='nsew')
self.new_product_tab = tk.Frame(self.notebook, bg='#233223' )
self.list_product_tab = tk.Frame(self.notebook, bg='#323232')
self.edit_product_tab = tk.Frame(self.notebook, bg='#433434')
# Adding Tabs to Notebook
self.notebook.add(self.new_product_tab, text=" Add New Product ")
self.notebook.add(self.list_product_tab, text=" List All Product ")
self.notebook.add(self.edit_product_tab, text=" Edit Product ")
# -- there is an instance of DetailFrame created inside EditProductFrame
self.productframe = EditProductFrame(self.edit_product_tab)
# -- so don't create another instance of DetailFrame
#self.detailframe = DetailFrame(self.productframe)
button = tk.Button(self.list_product_tab, text="Change background in Edit Form", command=self.change_method)
button.pack()
def change_method(self):
print("Trying to change the frame")
self.productframe.raise_edit_frame(DetailFrame)
#self.detailframe.change_bg('green')
# -- call change_bg() on the instance of DetailFrame inside EditProductFrame
self.productframe.frames[DetailFrame].change_bg('green')
self.notebook.select(self.edit_product_tab)
Another option is to make self.detailframe the reference to the instance of EditFrame inside EditProductFrame:
self.detailframe = self.productframe.frames[DetailFrame]

Tkinter Treeview not showing newly inserted items, no errors given

I'm building a desktop application that lets you insert some data into a form and then the data is displayed in a series (3) of Treeview widgets.
This is the form that I'm using to enter new data:
It's in a Toplevel widget. When the Add button is pressed the new data is stored in a file and it also should insert the new data in the corresponding Treeview Widget.
This is the root window:
It's comprised of 3 Treeview widgets. The purpose of the application is to give the user the opportunity to sort candidates into the right Treeview widget.
The issue that I'm facing is that when the Add button is pressed the new data is not shown in the Treeview widget and no errors are given. I think it may be an issue of class instantiation. This is an excerpt from my app, please see below a Minimal, Complete and Verifiable example
class MainApp(tk.Tk):
def __init__(self, *args, **kwargs):
...
# frame and menu classes are instantiated here
self.FrameList = {ViableCandidates: ViableCandidates(self),
NotViableCandidates: NotViableCandidates(self),
InProgressCandidates: InProgressCandidates(self)}
...
def InstanceLinker(self, frame):
link = self.FrameList[frame]
return link
class GUIMenu(tk.Menu):
def __init__(self, parent):
...
# menu code is here
addcandidates.add_command(label='Quick Add', command=lambda: QuickAdd(parent))
class QuickAdd(tk.Toplevel):
def __init__(self, parent):
...
# code for the small Toplevel window
...
# this is the code that I use to add the new item to Treeview when the Add button is pressed
if CandidateInfo['status'] == 'Viable':
app.InstanceLinker(ViableCandidates).AddtoList()
elif CandidateInfo['status'] == 'Not Viable':
app.InstanceLinker(NotViableCandidates).AddtoList()
else:
app.InstanceLinker(InProgressCandidates).AddtoList()
# ViableCandidates, NotViableCandidates, InProgressCandidates are created with the same pattern
class InProgressCandidates(tk.Frame):
def __init__(self, parent):
global Counter
tk.Frame.__init__(self, parent)
self.columnconfigure(0, weight=1)
self.rowconfigure(1, weight=1)
title = tk.Label(self, text="Candidates In Progress", font="Verdana 10 bold")
title.grid(row=0, column=0, sticky='nesw')
self.tree = ttk.Treeview(self)
self.tree.grid(row=1, column=0, sticky='nesw')
scrollbar = ttk.Scrollbar(self, orient='vertical', command=self.tree.yview)
scrollbar.grid(row=1, column=1, sticky='nws')
self.tree.config(columns=('Name', 'Date'), selectmode='browse', height=20, yscrollcommand=scrollbar.set)
self.tree.column('#0', width=20, minwidth=10, stretch=tk.YES)
self.tree.column('Name', width=150, minwidth=10, stretch=tk.YES)
self.tree.column('Date', width=80, minwidth=10, stretch=tk.YES)
self.tree.heading('#0', text='#', anchor=tk.W)
self.tree.heading('Name', text='Name', anchor=tk.W)
self.tree.heading('Date', text='Date', anchor=tk.W)
if Counter < 4:
Counter += 1
self.PopulateList()
def PopulateList(self):
selection = Database().SelectFromDB('name, date', "status = 'In progress'")
for i in range(len(selection)):
name = list(selection[i])[0]
date = adjusttotimezone(list(selection[i])[1])
self.tree.insert("", i, name, text=i + 1)
self.tree.set(name, 'Name', name)
self.tree.set(name, 'Date', date)
CandidateCounter['InProgressCandidates'] = i
def AddtoList(self):
CandidateCounter['InProgressCandidates'] += 1
print('I was here')
self.tree.insert("", CandidateCounter['InProgressCandidates'], CandidateInfo['name'],
text=CandidateCounter['InProgressCandidates'])
self.tree.set(CandidateInfo['name'], 'Name', CandidateInfo['name'])
selection = Database().SelectFromDB('date', "name = '" + CandidateInfo['name'] + "'")
date = adjusttotimezone(list(selection[0])[0])
self.tree.set(CandidateInfo['name'], 'Date', date)
app = MainApp()
app.mainloop()
When the "Add" button is pressed there are no errors and "I was here" is printed so the AddtoList method is instantiated, but there are no new items added to Treeview. I did check if the variables that I'm using to create the new Treeview item hold the correct data and they do.
EDIT: This is a Minimal, Complete and Verifiable example:
import tkinter as tk
from tkinter import ttk
Bigbadtext = ''
Counter = 0
class MainApp(tk.Tk):
def __init__(self, *args, **kwargs):
self.MainWindow = tk.Tk.__init__(self, *args, **kwargs)
menu = GUIMenu(self)
self.config(menu=menu)
frame = InProgressCandidates(self)
frame.grid(row=0, column=1, sticky='nesw')
self.FrameList = {InProgressCandidates:InProgressCandidates(self)}
def InstanceLinker(self, frame):
link = self.FrameList[frame]
return link
class GUIMenu(tk.Menu):
def __init__(self, parent):
tk.Menu.__init__(self, parent)
addcandidates = tk.Menu(self, tearoff=0)
self.add_cascade(label='Add Candidates', menu=addcandidates)
addcandidates.add_command(label='Quick Add', command=lambda: QuickAdd(parent))
class QuickAdd(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
saysomething = tk.Entry(self)
saysomething.grid(row=1, column=0)
def addbutton():
global Bigbadtext
Bigbadtext = saysomething.get()
app.InstanceLinker(InProgressCandidates).AddtoList()
okbutton = ttk.Button(self, text='Add', command=addbutton)
okbutton.grid(row=2, column=0)
class InProgressCandidates(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.tree = ttk.Treeview(self)
self.tree.grid(row=1, column=0, sticky='nesw')
scrollbar = ttk.Scrollbar(self, orient='vertical', command=self.tree.yview)
scrollbar.grid(row=1, column=1, sticky='nws')
self.tree.config(columns='something', selectmode='browse', height=20, yscrollcommand=scrollbar.set)
self.tree.column('#0', width=20, minwidth=10, stretch=tk.YES)
self.tree.column('something', width=150, minwidth=10, stretch=tk.YES)
self.tree.heading('#0', text='#', anchor=tk.W)
self.tree.heading('something', text='Say something', anchor=tk.W)
def AddtoList(self):
global Counter
Counter += 1
print('I was here')
self.tree.insert("", Counter, Bigbadtext, text=Counter)
self.tree.set(Bigbadtext, 'something', Bigbadtext)
app = MainApp()
app.mainloop()
The problem is that you are creating two treeview widgets, and then adding items to the one that is invisible.
You create one here:
frame = InProgressCandidates(self)
Then you create another one here:
self.FrameList = {InProgressCandidates:InProgressCandidates(self)}
Since you've already created one, the one you created should be what goes in self.FrameList:
self.FrameList = {InProgressCandidates:frame}
It is not really an answer but I up voted the question because it solved me a problem. I wanted to add items to the widget but did not want to show it to the user until I finished to populate the tree. But each insert showed right away. Now I create 2 identical widgets, one visible and the other is not, and once it is populated I change between them. Thus even a mistake can have a benefit.

Failed to access the variable from another class in Tkinter

I am trying to develop a Tkinter GUI which contains two pages, the first one to input stock name (counter_selection) and the second one to plot stock price data. However, when I tried to use the data input from the first class using controller.get_page function it does not return the value input by Entry. The code is as below:
class Application(Tk.Tk):
'''A GUI Application for FCN Demo'''
def __init__(self):
'''Initialization of frame'''
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 (StartPage, counter_selection, plot_counter):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row= 0, column = 0, sticky = "nsew")
self.show_frame(StartPage)
def get_page(self,classname):
'''Returns an instace of page given it's class name as a string'''
for page in self.frames.values():
if str(page.__class__.__name__) == classname:
return page
return None
def show_frame(self,cont):
frame = self.frames[cont]
frame.tkraise()
class counter_selection(Tk.Frame):
def __init__(self, parent, controller):
'''This is to create the widgets to input stock'''
Tk.Frame.__init__(self,parent)
self.controller = controller
label1 = Tk.Label(self, text = "Please Enter 3 counter names")
label1.pack(padx = 10, pady = 10)
self.entry1 = Tk.Entry(self)
self.entry1.pack()
self.entry2 = Tk.Entry(self)
self.entry2.pack()
self.entry3 = Tk.Entry(self)
self.entry3.pack()
button1 = Tk.Button (self, text = 'Confirm', command = lambda: controller.show_frame(plot_counter))
button1.pack()
class plot_counter(Tk.Frame):
'''This is to plot the graph of three selected counters'''
def __init__(self,parent,controller):
Tk.Frame.__init__(self,parent)
self.controller = controller
counterPage = self.controller.get_page('counter_selection')
self.counter1 = counterPage.entry1.get()
self.counter2 = counterPage.entry2.get()
self.counter3 = counterPage.entry3.get()
label1 = Tk.Label(self, text = self.counter11)
label1.pack()
The label1 does not show anything on the Frame, suggesting that it seems failed to get the value from the class. What's my mistake?
(PS: I didn't put the StartPage in since it is irrelevant for the question)
Why is Tkinter Entry's get function returning nothing?
TL;DR: your call to entry1.get() is done only once while instancing the plot_counter object, and never called again. Try putting it in a function to be called when displaying the Frame.

OOP Tkinter Python OOP - From one class how do i run a method in another class?

I want to have a frame with a input box, when the submit button next to it is pressed it submits the input and calls the function Add_Label from class ChangedPage where the function is added under and then raises the next frame where I want the label to appear.
The problem is I want the function I call to add a widget to the second class not the class I am calling it from. I have made a new program to demonstrate this issue:
import tkinter as tk
class Creating_Stuff(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 (PageTwo, ChangedPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(PageTwo)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
global bob
self.tk_setPalette(background='#bbfff0', foreground='black',
activeBackground='#d9d9d9', activeForeground='#ff9933')
self.controller = controller
title_2 = tk.Label(self, text= "Input Page #frame1", font = "Times 17 underline bold").place(relx = 0.5, rely = 0.1, relwidth = 0.4, anchor="center")
self.ENTRY = tk.Entry(self,width = 10, bg='white', fg='Black')
self.ENTRY.place(relx=0.5,rely=0.5,relwidth=0.3,anchor="center")
self.submit_button_2 = tk.Button(self, text="Submit Request", bg='#f2f2f2', command= self.raising)
self.submit_button_2.place(relx=0.4,rely=0.6,relwidth=0.2,anchor="center")
####Generic Stuff
Exit = tk.Button(self, text ="Exit",command=lambda:destroy(),bg='#f2f2f2')
Exit.place(relx=0.5,rely=(9/10), relwidth = 0.4, anchor="center")
def raising(self):
#Memes and stuff goes here
global bob
bob = self.ENTRY.get()
ChangedPage.Add_Label(self)
self.controller.show_frame(ChangedPage)
class ChangedPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
###Main stuff for the frames
self.title_27 = tk.Label(self, text= "Example Changed Page? #frame2", font = "Times 16 italic underline"
).place(relx = 0.36, rely = 0.05, relwidth = 0.4, anchor=tk.CENTER)
####Generic Stuff
bk2_Menu2 = tk.Button(self, text="Back to start",
command=lambda: controller.show_frame(PageTwo),bg='#f2f2f2')
bk2_Menu2.place(relx=0.5,rely=(0.8), relwidth = 0.2, anchor="center")
Exit = tk.Button(self, text ="Exit",command=lambda:destroy(),bg='#f2f2f2')
Exit.place(relx=0.5,rely=(9/10), relwidth = 0.4, anchor="center")
def Add_Label(self):
self.label1 = tk.Label(self, text= bob, font="Times 20")
self.label1.place(anchor = tk.CENTER, relx = 0.5, rely = 0.5, relwidth = 0.2, relheight = 0.1)
app = Creating_Stuff()
def destroy():
app.destroy()
app.tk_setPalette(background='#bbfff0', foreground='black',
activeBackground='#d9d9d9', activeForeground='#ff9933')
app.title("Example Label Adding Program")
app.geometry("800x450")
app.mainloop()
The code I have shown is a program with the first page with input boxes when you submit it will take you to the next frame and run the function from class ChangedPagewhich is meant to make the label on the page that is just raised. Can anyone see what is going wrong?
Start Page is here:
Where it is meant to go here:
The first step is to update your controller to be able to return an instance of a page.
class Creating_Stuff(tk.Tk):
...
def get_page(self, page_class):
return self.frames[page_class]
Next, you need to call this function to get a reference to the instance which was created earlier, from which you can call the method:
class PageTwo(tk.Frame):
...
def raising(self):
...
changed_page = self.controller.get_page(ChangedPage)
changed_page.AddLabel()
...
You are calling a class method, there is no instance attached for it to work on.
For example, to create class Creating_Stuff(tk.Tk): you use the code
app = Creating_Stuff()
This gives you an instance of Creating_Stuff called app. Hence you call app.destroy()
It may be you created an instance of class ChangedPage in some of the code you took out, but you don't refer to that instance.
At some point (probably in the __init__ of PageTwo) you'd need something like:
MyWorkingPage = ChangedPage()
and then in def raising(self):
MyWorkingPage.Add_Label()
self.controller.show_frame(MyWorkingPage)
Now the code will now which ChangedPage you want to display.
There are such things as staticmethod and classmethod, but they aren't what you want here.

Double-lambda Python

Is it feasible to execute multiple statements in a lambda? The app_data dictionary in my controller has a property "listbox" that stores a selected listbox value. A button on SelectPage has a lambda command that changes frames to NextPage.
Can the frame switch and app_data set statements both occur in a double-lambda? If so, what might this look like? Or do these operations have to occur separately (i.e. in a button click event)?
Update #1:
Issue isolated to lambda expression since just putting a constant (i.e. 1) in like .set(1) does not save either.
Update #2:
Using a lambda with doStuff(controller) solves the problem somewhat, but the listbox reference throws an error:
value = self.listbox.get(self.listbox.curselection())
AttributeError: 'SelectPage' object has no attribute 'listbox'
button:
button1 = ttk.Button(self, text='Next Page',
command=lambda:self.doStuff(controller))
doStuff():
def doStuff(self,controller):
controller.show_frame(NextPage)
controller.app_data["listbox"].set(1)
value = self.listbox.get(self.listbox.curselection())
print(value)
print("do Stuff successful")
Full Code:
from tkinter import *
from tkinter import ttk
class MyApp(Tk):
# Controller class
def __init__(self):
Tk.__init__(self)
# App data in controller
self.app_data = {"listbox": StringVar(),
"entry": StringVar(),
}
container = ttk.Frame(self)
container.pack(side="top", fill="both", expand = True)
self.frames = {}
for F in (SelectPage, NextPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky = NSEW)
self.show_frame(SelectPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def get_page(self,classname):
for page in self.frames.values():
if str(page.__class__.__name__) == classname:
return page
return None
class SelectPage(ttk.Frame):
def __init__(self, parent, controller):
self.controller = controller
ttk.Frame.__init__(self, parent)
ttk.Label(self, text='Select Page').grid(padx=(20,20), pady=(20,20))
listbox = Listbox(self,exportselection=0)
listbox.grid()
for item in [0,1,2,3,4,5]:
listbox.insert(END, item)
print (item)
entry1 = ttk.Entry(self, textvariable=self.controller.app_data["entry"], width=8)
entry1.grid()
button1 = ttk.Button(self, text='Next Page',
command=lambda:self.doStuff(controller)) # something like this lambda concept
button1.grid()
def doStuff(self,controller):
controller.show_frame(NextPage)
controller.app_data["listbox"].set(1)
value = self.listbox.get(self.listbox.curselection())
print(value)
print("do Stuff successful")
class NextPage(ttk.Frame):
def __init__(self, parent, controller):
self.controller = controller
ttk.Frame.__init__(self, parent)
ttk.Label(self, text='Next Page').grid(padx=(20,20), pady=(20,20))
button1 = ttk.Button(self, text='Select Page',
command=lambda: controller.show_frame(SelectPage))
button1.grid()
button2 = ttk.Button(self, text='press to print', command=self.print_it)
button2.grid()
def print_it(self):
value = self.controller.app_data["listbox"].get()
print ('The value stored in StartPage some_entry = ' + str(value))
value = self.controller.app_data["entry"].get()
print ('The value stored in StartPage some_entry = ' + str(value))
app = MyApp()
app.title('Multi-Page Test App')
app.mainloop()
Your original lambda didn't work because show_frame(...) returns None, which short-circuits the and so the remaining expression doesn't execute. Just change it to an or. Since the event caller doesn't care what you return, you could also create a two item tuple for the two subexpressions. There are other problems, such as self.listbox doesn't exist, which I fixed, but others remain.
As a pedantic aside, lambda only accepts an expression, which is a subset of a statement.
self.listbox = listbox
button1 = ttk.Button(self, text='Next Page',
command=lambda: controller.show_frame(NextPage) or self.controller
.app_data["listbox"]
.set(self.listbox.get(self.listbox.curselection()))) # something like this lam
Is it feasible to execute multiple statements in a lambda?
No. In general, you should avoid using lambda at all, and when you must use it you need to keep it as brief as possible. If you need to call more than a single function, create a new function that calls the other functions.
Also, if you're passing in an argument that is just an attribute of the object, you don't need lambda at all. I think the following code is much easier to read, much easier to understand, much easier to write, and thus much easier to maintain:
button1 = ttk.Button(self, text='Next Page',command=self.doStuff)
...
def doStuff(self):
self.controller.show_frame(NextPage)
self.controller.app_data["listbox"].set(1)

Categories