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)
Related
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
I am creating a program with two frames in one window. The first has input fields, the second will create a graph.
I found a way to create input fields dynamically from a list and get their values accordingly, but I can't get them to show on the window. When I run the program it shows an empty window.
What should I do to get the label and input widgets to show on the first frame (InputPage)? I tried changing 'parent' to 'self' but it made no difference. I don't really understand the structure of widgets in multiple frame applications.
Here is my code:
from tkinter import *
namesInput = ["first", "second", "third", "fourth", "fifth"]
entryInput = {}
labelInput = {}
root = Tk()
class ZorgplanGrafiek(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 (InputPage, GraphPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(InputPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class InputPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self,parent)
label = Label(self, text="Zorgplan input")
label.pack(pady=10,padx=10)
i = 0
for name in namesInput:
e = Entry(self)
entryInput[name] = e
lb = Label(self, text=name)
labelInput[name] = lb
i += 1
#def print_all_entries():
# for name in namesInput:
# print( entryInput[name].get())
class GraphPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
label = Label(self, text="The graph will show here")
label.pack(pady=10,padx=10)
button = Button(self, text="Back to Home",
command=lambda: controller.show_frame(InputPage))
button.pack()
app = ZorgplanGrafiek()
app.mainloop()
Firstly delete root = Tk() at the top of your code you are creating 2 windows. Secondly, your loop to create the entry and label widgets is not correct therefore they are not displayed on the frame, so that is your answer for why they wont show.
Try this:
class InputPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self,parent)
label = Label(self, text="Zorgplan input")
label.grid(row=0, column=0, sticky ='n', columnspan =2)
# i brought your variable in the class for example sake
namesInput = ["First:", "second:", "Third:", "Fourth:", "Fifth:"]
self.entryWidgets = [] # we want to call this in another function so we assign it as self.variableName
labelWidgets = []
#LOOP TO CREATE WIDGETS
for i in range(0, len(namesInput)):
labelWidgets.append(Label(self, text = namesInput[i]))
self.entryWidgets.append(Entry(self))
labelWidgets[-1].grid(row= i+1, column =0, sticky='e')
self.entryWidgets[-1].grid(row= i+1, column = 1, sticky='w')
submit = Button(self, text = "Submit", command = self.getEntries)
submit.grid(row = 6, column =0, columnspan =2)
def getEntries(self):
results = []
for x in self.entryWidgets: # i.e for each widget in entryWidget list
results.append(x.get())
print(results)
Code explanation:
We are iteratively creating widgets to the number of elements within namesInput list. Each time we create a widget we add it to their respective list. E.g for entry widgets we created a list called entryWidgets. We append them to a list so that we can reference them individually later on when we want to do something with them.
Furthermore, i changed pack() to grid(). The grid method is much cleaner and gives us more control over the layout of our window in my opinion.
Note - If you're struggling to understand how i 'grided' the widgets in the way i did, i just drew up a quick sketch of the widgets with co-ordinates representing their row and column and then from there its fairly easy to see how to manipulate the grid settings in the for loop.
Screenshot:
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.
I am new to Python and am writing a code to automate certain measurement equipment. I am going to mention only a small part of my code to keep it concise.
The first class VSWR is used to select different frames. Since I need to change frames and go back and forth in them, I made a class VSWR. This class calls Start Page , which has a button "Path Loss". After clicking this button, the user needs to enter certain parameters, and in this case "Start and Stop Frequencies". Clicking the OK button will show what the user has entered and then asks the user to confirm it. After confirming a text window opens in a new frame(which is the RunModuleTests class). I will write all my remaining automation code in this class. But for now I want the power supply to turn on(for now I am using insert command message to show that my power supply is turned on) after 4 secs after I hit the confirm button. But what is happening is the RunModuleTests class executes as soon as I run the whole code and by the time I reach to my text window after entering the parameters, power supply will already be turned on.
What I think is happening is that as soon as I hit run on my whole code, the mainloop starts the execution of all the frames. (Please correct me if I am wrong here), whereas I want my frames(or classes and their functions) to execute only when those classes are called by clicking the button and not when I hit Run for the whole code. Is there any work around to this ??
Please let me know if someone needs me to elaborate my question or need more details of the issue I am facing here.
Thanks
import Tkinter as tk
from Tkinter import DoubleVar, IntVar, StringVar
import ttk
from numpy import arange
LARGE_FONT= ("Verdana", 12)
class VSWR(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "VSWR")
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, PathLoss, RunModuleTests):
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(self, text="This is the start page", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button3 = ttk.Button(self, text="Path Loss",
command=lambda: controller.show_frame(PathLoss))
button3.pack()
class PathLoss(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
labelPathLoss = ttk.Label(self, text = 'Path Loss Measurement:', font=LARGE_FONT)
labelPathLoss.pack(pady=10,padx=10)
global X, Y
X = 20
Y = 100
#Define Variables with Type
self.startFreq = DoubleVar()
self.stopFreq = DoubleVar()
self.freqInc = IntVar()
labelenterStartFreq = ttk.Label(self, text = 'Enter the Start Frequency (in MHz):')
labelenterStartFreq.place(x = X, y = Y+20)
labelenterStopFreq = ttk.Label(self, text = 'Enter the Stop Frequency (in MHz):')
labelenterStopFreq.place(x = X, y = Y+40)
entryStartFreq = ttk.Entry(self, textvariable = self.startFreq)
entryStartFreq.place(x = X+240, y = Y+20)
entryStopFreq = ttk.Entry(self, textvariable = self.stopFreq)
entryStopFreq.place(x = X+240, y = Y+40)
buttonOK = ttk.Button(self, text = 'OK', command = lambda: self.getValues(X,Y, controller))
buttonOK.place(x = X+240, y = Y+270)
def getValues(self,X,Y, controller):
getStartFreq = self.startFreq.get()
getStopFreq = self.stopFreq.get()
ttk.Label(self, text = 'You entered the following values:').place(x = X+580, y = Y)
ttk.Label(self, text = 'Start Frequency : %5.2f' %getStartFreq).place(x = X+580, y = Y+20)
ttk.Label(self, text = 'Stop Frequency : %5.2f' %getStopFreq).place(x = X+580, y = Y+40)
buttonConfirmPL = ttk.Button(self, text = 'Confirm', command = lambda: controller.show_frame(RunModuleTests))
buttonConfirmPL.place(x = X+580, y = Y+300)
class RunModuleTests(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.rootText = tk.Text(self)
self.rootText.place(x = 200, y = 100)
self.rootText.tkraise()
opening = '\nProceeding to measure TRX path loss for FWD/REV....\n'
self.rootText.insert("insert", opening )
self.rootText.after(4000, self.temp)
def temp(self):
self.rootText.insert("insert", '\nTurning Power supply ON...\n')
app = VSWR()
app.geometry('1000x600+150+100')
app.mainloop()
The reason the power supply turn-on message appears immediately is this: At the very start of your program you create an instance of StartPage, PathLoss and RunModuleTests. In the __init__ method of RunModuleTests you're calling self.rootText.after(4000, self.temp), and self.temp writes that message. Thus, four seconds after your program starts, that message appears.
There are many solutions, but to do it properly involves a lot of rewriting. The structure of your code makes a simple solution difficult. The most common things you can try is to either a) not create an instance of RunModuleTests until you actually need it, or b) move the call to after into some other function, and then call that function only after you make that frame visible.
Personally I would do the latter -- you need to decouple the creation of the frame from the functions that can be done when the frame is visible. That means you'll need to add some extra logic to show_frame, or replace the call to show_frame with something else.
For background: most of my experience is ruby/rails. I'm trying to help a friend by building a simple GUI app that updates an Excel file and don't have much experience w/ Python or TKinter. The goal is to have a simple form, the user enters a number, and another form is shown with a drop down menu. I decided to store the given number in a global variable as I have had trouble trying to pass a variable between the two frames. I cannot manage to both set the global variable and switch to the second frame. Other questions/issues I've had are in ## marked comments.
Alternatively, if anyone has any ideas on the best way to make a cross platform app that can access an MDB or excel file, I'm all ears. It kind of blows me away how difficult this has been. Thanks for any help.
import Tkinter as tk
TITLE_FONT = ("Helvetica", 18, "bold")
ID_NUMBER = None
class StatusApp(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 (EntryPage, StatusPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(EntryPage)
def show_frame(self, c):
'''Show a frame for the given class'''
frame = self.frames[c]
frame.tkraise()
class EntryPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Enter ID:", font=TITLE_FONT)
self.entry = tk.Entry(self)
## Using the lambda works to switch frames, but I need to be able to execute
## multiple statements.
# entry.bind('<Return>', lambda event: controller.show_frame(StatusPage))
## In examples I've seen, callback has been used without the empty parens, not sure
## why they're needed?
self.entry.bind('<Return>', self.callback())
label.pack(side="top", fill="x", pady=10)
self.entry.pack()
self.entry.focus_set()
def callback(self):
## I noticed the following gets fired once the program starts
print 'hello'
## For some reason it says that entry doesn't have the attribute 'set'. I don't
## understand this as I'm calling it like a method.
self.entry.set('hello')
## Ultimately setting the global ID_NUMBER variable is one of the main goals of this
## function
ID_NUMBER = self.entry.get()
## I haven't been able to switch frames from within this function, only w/ a lambda as
## seen on line 34.
# show_frame(StatusPage())
class StatusPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="ID: ", font=TITLE_FONT)
optionList = ('train', 'plane', 'boat')
selected_opt = tk.StringVar()
selected_opt.set(ID_NUMBER)
menu = tk.OptionMenu(self, selected_opt, *optionList)
button = tk.Button(self, text="Save", command=lambda: controller.show_frame(EntryPage))
label.pack(side="top", fill="x", pady=10)
menu.pack()
button.pack()
if __name__ == "__main__":
app = StatusApp()
app.mainloop()
The object of a binding must be a reference to a callable function. Lambda is often used in this context because it creates an anonymous function and returns a reference).
When you do ...bind(..., self.callback()), you are calling that function at the time the bind statement executes. In the code you make the comment ## I noticed the following gets fired once the program starts; this is why. The result of this function call is what is associated with the binding. Quite often, and in your specific case, this is the value None. You must omit the ()
In the code comments you wrote
## For some reason it says that entry doesn't have the attribute 'set'. I don't
## understand this as I'm calling it like a method.
self.entry.set('hello')
What makes you believe an entry widget has a set method? No documentation that I know of makes that claim. The error message is correct, the entry widget has no attribute named "set" (functions are considered attributes in this context).