How to reference correctly to frame names in tkinter - python

I am creating a card game in tkinter and need help with referencing to the frame names. My problem is that when I want to "refresh" the frame, I need to destroy and recreate it and this changes the progressive numbering of the frames.
Please take a look at the code below. The example shows that the third frame every time gets a new name as it gets recreated.
import tkinter as tk
class RootFrame(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.main_window = tk.Frame(self)
self.main_window.pack(side="top", fill="both", expand=True)
self.main_label = tk.Label(self.main_window, text="Main Window")
self.main_label.pack()
self.second_frame = SecondFrame(self.main_window, self)
self.second_frame.pack()
class SecondFrame(tk.Frame):
def __init__(self, parent, controller, *args, **kwargs):
super().__init__(*args, **kwargs)
self.controller = controller
label = tk.Label(self, text="Second Frame")
label.pack()
self.create_third_frame()
def create_third_frame(self):
self.third_frame = ThirdFrame(self, self.controller)
self.third_frame.pack()
def update_frame(self):
self.third_frame.destroy()
self.create_third_frame()
class ThirdFrame(tk.Frame):
def __init__(self, parent, controller, *args, **kwargs):
super().__init__(*args, **kwargs)
self.controller = controller
self.parent = parent
label = tk.Label(self, text="Third Frame")
label.pack()
refresh_button = tk.Button(
self, text="Resfresh", command=self.parent.update_frame)
refresh_button.pack()
print(self.winfo_name())
if __name__ == "__main__":
app = RootFrame()
app.mainloop()
The code above is used to illustrate the problem. Please run the code and you'll see the changing widget name in the terminal.
I use winfo_parent and winfo_name in my actual code to create different conditions for button bindings. For example, if the user clicks a widget1 in frame6 happens X and when I click a widget8 in frame2 happens Y. This works until I destroy() and recreate something and everything breaks.
I suppose that using winfo_name and winfo_parent for this kind of referencing is not the correct way to get around, but I really can't think of anything else.

I'm not sure exactly what you are asking, but you can assign a specific name to the widget:
def create_third_frame(self):
self.third_frame = ThirdFrame(self, self.controller, name='testframe')
self.third_frame.pack()
Then each time the name of the frame created will be consistent.
You can also reference the widget by name with Tk().nametowidget(), see this relevant answer here: Is it possible to search widget by name in Tkinter?
>>> from Tkinter import *
>>> win = Tk()
>>> button = Button( Frame( win, name = "myframe" ), name = "mybutton" )
>>> win.nametowidget("myframe.mybutton")
<Tkinter.Button instance at 0x2550c68>
I would recommend sticking with a OOP approach however, and just reference it with from your code like self.thirdframes where you might have a list or dict of ThirdFrame objects. This way your python code can easily reference the objects without going back to the tcl interpreter and parse the widget name. If you ever will only have one ThirdFrame, then just reference back to self.thirdframe whenever you need it.

Related

Indicating the target of button define in class

I am green in Python, after going through books and the questions here, I tried to code the following. The following is my simplified version of my code. this is a script for GUI. In my design, there would be a "back" button on the top, and 10 page buttons below it. And when I clicked the button, it will go to another page. But I replace it by showing the string of the picture instead.
However, I found that the "back" button is at the bottom now. I believe it goes wrong on Line 12.
I tried as "PageButton=ttk.Button(self, text=self.i, command=lambda: print(self.PicLink))" as well, but all page buttons disappeared. I am totally lost on what should be set to get back my designed pattern. Although it still work funtionally, I would like to know how should I edit to meet my orignial dseign, So I can improve my coding knowledge and problem solving skill. Thanks for all your effort in advance.
import tkinter as tk
from tkinter import ttk
class PageButton(ttk.Frame):
def __init__(self, i, PicLink):
self.i = i+1
self.row=i//3+1
self.column=i%3
self.PicLink=PicLink
super().__init__()
print(self.i, self.row, self.column)
PageButton=ttk.Button(text=self.i, command=lambda: print(self.PicLink))
PageButton.grid(row=self.row, column=self.column)
class ViewerFrame(ttk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
ViewerFrame.backButton=ttk.Button(self, text="Back", command=lambda: BackPage(parent))
ViewerFrame.backButton.grid(row=0, column=0)
for i in range(10):
PicLink="abc%04i.jpg" % (i+1)
globals()['Button%s' % (i+1)]=PageButton(i, PicLink)
class ViewerPage(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.title("Viewer")
self.resizable(width=False, height=False)
ViewerFrame(self).grid(sticky=(tk.E+tk.W+tk.N+tk.S))
def ViewerWindow():
app=ViewerPage()
app.mainloop()
if __name__=="__main__":
ViewerWindow()
Since you do not specify the parent when creating the button inside PageButton class, it will be a child of the root window instead and put inside the root window instead of instance of PageButton.
Those page buttons are created and put into the root window before the instance of ViewerPage class which includes the Back button, so the Back button is being put after those page buttons.
So it is better to specify the parent when creating those page buttons and also PageButton should inherit from ttk.Button directly instead of creating an instance of it inside.
Below is the modified code:
import tkinter as tk
from tkinter import ttk
# inherit from ttk.Button directly
class PageButton(ttk.Button):
# added parent argument
def __init__(self, parent, i, PicLink):
self.i = i+1
self.row=i//3+1
self.column=i%3
self.PicLink=PicLink
print(self.i, self.row, self.column)
# specify the parent
super().__init__(parent, text=self.i, command=lambda: print(self.PicLink))
self.grid(row=self.row, column=self.column)
class ViewerFrame(ttk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.backButton=ttk.Button(self, text="Back", command=lambda: BackPage(parent))
self.backButton.grid(row=0, column=0)
# use a dictionary to store those page buttons
self.buttons = {}
for i in range(10):
PicLink="abc%04i.jpg" % (i+1)
self.buttons[f'Button{i+1}'] = PageButton(self, i, PicLink) # specify the parent
class ViewerPage(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.title("Viewer")
self.resizable(width=False, height=False)
ViewerFrame(self).grid(sticky=(tk.E+tk.W+tk.N+tk.S))
def ViewerWindow():
app=ViewerPage()
app.mainloop()
if __name__=="__main__":
ViewerWindow()

Creating a new window on pressing a button in an existing window in Tkinter

I am having issues with a Tkinter GUI. I need to create a large application. I need to use classes for that to manage all modules together. For a unit check and getting help here, I have tried to provide a sample which is close to exact problem ( with a small example) here:
I am creating a 1st window with a Button labelled as "Test". What I want is that when I click the button "Test", a new second window will pop up with a text "Enter Value" and entry space, where I can enter the value. I have provided the code below. What is happening is that, I am able to get the new window, but the text "Enter Value" and entry Space is generated in the first window instead of the second and the second window remains blank. I am not understanding where I am making the wrong logic call. Help will be very much appreciated.
I know we do not need classes for GUI applications, however to manage my large application ( not shown here), I need to have classes and I will very much appreciate, if some Tkinter Guru can help me with the bug in my code.
gui view File (gui_view.py)
import tkinter as tk
from tkinter import Tk
class MyMainGUI(tk.Frame):
def __init__(self, controller):
tk.Frame.__init__(self)
self.pack()
self.controller = controller
self.Button1=tk.Button(self)
self.Button1["text"]= "Test"
self.Button1["command"]=self.controller.buttonPressed1
self.Button1.grid(row=2,column=0,rowspan=2)
class MySecondGUI(tk.Frame):
def __init__(self, controller):
tk.Frame.__init__(self)
self.pack()
self.controller = controller
self.outputLabel2 = tk.Label(self)
self.outputLabel2["text"] = ("Enter Value")
self.outputLabel2.grid(row=1,rowspan=2)
#Entry Space
self.entrySpace2 = tk.Entry(self)
self.entrySpace2.grid(row=2,column=0,rowspan=2)
### gui Controller File (gui_controller.py)
import tkinter as tk
import gui_view # the VIEW file
class MainControl:
def __init__(self):
self.root = tk.Tk()
self.root.geometry('550x200')
self.view = gui_view.MyMainGUI(self)
self.view.mainloop()
def newWindow(self):
self.newWin = tk.Toplevel(self.root)
self.newWin.geometry('300x400')
self.newDisplay = tk.Label(self.newWin, text='Test Mode')
self.viewNew = gui_view.MySecondGUI(self.newWin)
self.viewNew.mainloop()
self.newDisplay.pack()
def buttonPressed1(self):
self.newWindow()
if __name__ == "__main__":
c = MainControl()
#
ADDING A MODIFIED CODE WITH NEW PROBLEM.
I have now been able to generate a code which pops up a new window with entries, when I click the button "Test" in the first Window. However, I am having problems creating buttons in the scond window. The way I have it now, it pops an error to me saying "'MySecondGUI' object has no attribute 'buttonPressed2"
Help will be very much appreciated.
I have pasted my updated code below:
GUI_VIEW FILE ( gui_view.py)
import tkinter as tk
from tkinter import Tk
class MyMainGUI(tk.Frame):
def __init__(self, controller):
tk.Frame.__init__(self)
self.pack()
self.controller = controller
self.Button1=tk.Button(self)
self.Button1["text"]= "Test"
self.Button1["command"]=self.controller.buttonPressed1
self.Button1.grid(row=2,column=0,rowspan=2)
class MySecondGUI(tk.Toplevel):
def __init__(self):
tk.Toplevel.__init__(self)
self.outputLabel2 = tk.Label(self)
self.outputLabel2["text"] = ("Enter Value")
self.outputLabel2.grid(row=5,rowspan=2)
self.entrySpace2 = tk.Entry(self)
self.entrySpace2.grid(row=8,column=0,rowspan=2)
self.Button2=tk.Button(self)
self.Button2["text"]= "Try Me"
self.Button2["command"] = self.buttonPressed2
self.Button2.grid(row=14,column=0,rowspan=2)enter code here
GUI MAIN CONTROLLER FILE
import tkinter as tk
import gui_view # the VIEW file
class MainControl:
def __init__(self):
self.root = tk.Tk()
self.root.geometry('550x200')
self.view = gui_view_temp.MyMainGUI(self)
self.view.mainloop()
def newWindow(self):
self.viewNew = gui_view.MySecondGUI()
self.viewNew.geometry('300x400')
self.newDisplay = tk.Label(self.newWin, text='Test Mode')
self.viewNew.mainloop()
self.newDisplay.pack()
def buttonPressed1(self):
self.newWindow()
def buttonPressed2(self):
pass
if name == "main":
c = MainControl()
in MySecondGUI(tk.Frame):
def __init__(self, controller):
#Attach your frame to "secondGUI" (which is your second window)
tk.Frame.__init__(self, controller)
in class MainControl:
#I assume you're passing your parent window/frame as "controller"?
self.viewNew = MySecondGUI(self.newWin)
according to Python Tkinter Docs
https://docs.python.org/3.5/library/tkinter.html#mapping-basic-tk-into-tkinter
You should specify your parent window if you're not attach your widget to main window.
#Main window
root = tk.Tk()
#Second Window
newWin = tk.Toplevel(root)
#Attach to main window
tk.Label(text="This label attached to root").pack()
tk.Button(text="This button attached to root").pack()
#Second Window
tk.Label(newWin, text="This label attached to second window").pack()
tk.Button(newWin, text="This button attached to second window").pack()
also,
self.viewNew.mainloop()
#This will fail because you have to set everything up before mainloop()
#_tkinter.TclError: can't invoke "pack" command: application has been destroyed
self.newDisplay.pack()
Edit for update
You should put your
def buttonPressed2(self):
in class MySecondGUI, not in class MainControl.
class MySecondGUI(tk.Toplevel):
def __init__(self):
tk.Toplevel.__init__(self)
self.outputLabel2 = tk.Label(self)
self.outputLabel2["text"] = ("Enter Value")
self.outputLabel2.grid(row=5,rowspan=2)
self.entrySpace2 = tk.Entry(self)
self.entrySpace2.grid(row=8,column=0,rowspan=2)
self.Button2=tk.Button(self)
self.Button2["text"]= "Try Me"
#self means "MySecondGUI" not "MainControl" here
self.Button2["command"] = self.buttonPressed2
self.Button2.grid(row=14,column=0,rowspan=2)
def buttonPressed2(self):
pass

Dynamically instantiate pages Tkinter

I'm building a GUI with code that originally came from another stack exchange answer. I've modified it since I want to pass variables to following pages and have them display the values. As a way to do this, rather than display on a button event, I am trying to create the page with the show() method:
import Tkinter as tk
#import vpnRename12 as Rename
#import vpnRename_pullconfig as pullConfig
"""
Create multiple pages to go through in the same frame. Use lift() to bring
the desired page into view and stack them on top of one another. Define
page-specific methods in each page.
"""
class vpnRenameProgram(tk.Tk):
"""
Create a page class that will allow you to lift to front the applicable
page with the proper functions to keep the script running.
"""
def __init__(self,*args,**kwargs):
tk.Tk.__init__(self,*args,**kwargs)
# Create an empty dictionary to contain all of the page names
self.frames = {}
self.show("MainView")
# Instantiate the next page on call, rather than at start to allow
flexibility
def instantiate_page(self, cls):
"""
Since all of the pages are defined in the same scope/namespace, we
can use
the globals()[] dict to find and instantiate the pages dynamically
with the show() method.
cls is the class argument we are doing a lookup on in the global()
dict.
"""
try:
newframe = globals()[cls](container,self)
page_name = newframe.__name__
except:
print("\nError defining inline class %s"%cls)#("Class %s is not defined" %cls)
newframe = None
page_name=globals()[cls].__name__
return newframe, page_name
# Create lift function to bring desired page to front of view,
#instantiate page if
# it isn't already (check frames dict)
def show(self, cls):
if cls not in self.frames.keys():
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
frame, page_name = self.instantiate_page(cls)
if frame==None:
frame = globals()[cls](parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="news")
frame = self.frames[cls]
frame.lift()
def get_page(self, classname):
"""
Return instance of page when it's class name is passed in as string
"""
for page in self.frames.values():
if str(page.__class__.__name__) == classname:
return page
return None
class MainView(tk.Frame):
def __init__(self,parent, controller,**kwargs):
tk.Frame.__init__(self,parent)
self.controller = controller
self.edit_directory="edit_dir"
self.complete_directory ="comp_dir"
TitleLabel = tk.Label(self, text="VPN Script Editor")
TitleLabel.pack({"side":"top"})
EditLabel = tk.Label(self, text="Edit File Directory:
%s"%self.edit_directory)
EditLabel.pack()
CompLabel = tk.Label(self, text="Completed File Directory:
%s"%self.complete_directory)
CompLabel.pack()
Next = tk.Button(self, text="Next", command=lambda:
controller.show("listVPN"))
Next.pack()
class listVPN(tk.Frame):
"""
This is the second page, it contains a text box where you will list the
names of the vpn's that you want to edit. It will also display the
directories
obtained by the pullconfig script.
"""
def read_list(self):
vpn_list=str(self.var_vpn_list.get()).upper()
return vpn_list
def __init__(self, parent, controller, **kwargs):
self.controller = controller
tk.Frame.__init__(self, parent)
self.var_vpn_list = tk.StringVar()
label=tk.Label(self, text="Please list the VPNs desired to edit")
label.pack()
#Create text box to submit a list of vpns back to the main program
vpnLabel = tk.Label(self, text="VPN Names").pack()
self.TextBox = tk.Entry(self, textvariable=self.var_vpn_list)
self.TextBox.pack()
vpnListSubmit = tk.Button(self, text="Enter", command= lambda:
self.read_list() and self.controller.show("pickFiles"))
vpnListSubmit.pack()
class pickFiles(tk.Frame):
"""
Second page that allows you to select your desired files from the
edit directory specified in the config file. Check all desired files,
list will be returned to the program.
"""
def get_vpn_list(self):
list = self.controller.get_page("listVPN").var_vpn_list.get()
self.vpn_list = str(list).upper()
self.vpn_label.configure(text="VPN List: %s"%self.vpn_list)
return self.vpn_list
def __init__(self, parent, controller,**kwargs):
# Inherits from the tk.Frame class
tk.Frame.__init__(self, parent)
self.controller = controller
self.vpn_list = tk.StringVar()
list = self.controller.get_page("listVPN").var_vpn_list.get()
self.vpn_list = str(list).upper()
show_vpn = tk.Button(self, text="Show vpnlist", command =
self.get_vpn_list)
show_vpn.pack()
self.vpn_label = tk.Label(self, text="VPN List: %s" %self.vpn_list)
self.vpn_label.pack()
# todo: get external module function to run with variable input
#file_list = Rename.searchFile(vpnlist)
# Execute program on calling the parent class
if __name__=="__main__":
app = vpnRenameProgram()
app.mainloop()
EDIT:
Above is my whole code with custom scripts I've imported commented out. My main question is about layout. I want the frames to stack on top of one another, but they are not. Why is it doing this and what would get me on track to getting the layout I want?
The main problem with your code is that you're creating multiple containers. The code that served as a base for your program was specifically designed to have a single container with multiple frames within the container.
The first step is to create the container, and save a reference so it can be used later:
class vpnRenameProgram(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.container = tk.Frame(self)
self.container.pack(side="top", fill="both", expand=True)
self.container.grid_rowconfigure(0, weight=1)
self.container.grid_columnconfigure(0, weight=1)
self.frames = {}
self.show_frame("MainView")
I'm also going to recommend that you pass the actual class instead of a class name. This way you don't have to dig into globals() to try to find the right class based on the name.
Change the last line in the above to look like this:
self.show_frame(MainView)
You will also need to change get_page, but it's now a simple lookup:
def get_page(self, page_class):
return self.frames.get(page_class, None)
The final step is to redefine show to create the frame on demand. You've created a method called instantiate_page, but I see no real reason not to put it all in a single function since it's only a couple extra lines of code:
def show(self, page_class):
if page_class in self.frames:
frame = self.frames[page_class]
else
frame = page_class(parent=self.container, controller=self)
frame.grid(row=0, column=0, sticky="nsew")
self.frames[page_class] = frame
frame.tkraise()
That's all there is to it. You just need to remember to pass the class rather than the name of the class when calling show or get_page (eg: controller.show(listVPN), controller.get_page(pickFiles)`, etc)

Tkinter Class structure (class per frame) issue with duplicating widgets

Ive been trying out OOP for use with Tkinter - Im getting there (I think) slowly...
I wanted to build a structure where each frame is handled by its own class, including all of its widgets and functions. Perhaps I am coming from the wrong angle but that is what makes most logical sense to me. - Feel free to tell me if you agree / disagree!
I know why the problem is happening - when im calling each class my __init__ runs everytime and builds the relevant widgets regardless of whether they are already present in the frame. However, the only way I can think of getting round this would be to build each frame in the __init__ of my primary class GUI_Start. - Although this seems like a messy and un-organised soloution to the problem.
Is there a way I can achieve a structure where each class takes care of its own functions and widgets but doesn't build the frame each time?
See below for minimal example of the issue:
from Tkinter import *
class GUI_Start:
def __init__(self, master):
self.master = master
self.master.geometry('300x300')
self.master.grid_rowconfigure(0, weight=1)
self.master.grid_columnconfigure(0, weight=1)
self.win_colour = '#D2B48C'
self.frames = {}
for window in ['win1', 'win2']:
frame = Frame(self.master, bg=self.win_colour, bd=10, relief=GROOVE)
frame.grid(row=0, column=0, sticky='news')
setattr(self, window, frame)
self.frames[window] = frame
Page_1(self.frames)
def Next_Page(self, frames, controller):
controller(frames)
class Page_1(GUI_Start):
def __init__(self, master):
self.master = master
self.master['win1'].tkraise()
page1_label = Label(self.master['win1'], text='PAGE 1')
page1_label.pack(fill=X)
page1_button = Button(self.master['win1'], text='Visit Page 2...', command=lambda: self.Next_Page(self.master, Page_2))
page1_button.pack(fill=X, side=BOTTOM)
class Page_2(GUI_Start):
def __init__(self, master):
self.master = master
self.master['win2'].tkraise()
page2_label = Label(self.master['win2'], text='PAGE 2')
page2_label.pack(fill=X)
page2_button = Button(self.master['win2'], text='Back to Page 1...', command=lambda: self.Next_Page(self.master, Page_1))
page2_button.pack(fill=X, side=BOTTOM)
root = Tk()
gui = GUI_Start(root)
root.mainloop()
Feel free to critique the structure as I may be trying to approach this from the wrong angle!
Any feedback would be much appreciated!
Luke
The point of using classes is to encapsulate a bunch of behavior as a single unit. An object shouldn't modify anything outside of itself. At least, not by simply creating the object -- you can have methods that can have side effects.
In my opinion, the proper way to create "pages" is to inherit from Frame. All of the widgets that belong to the "page" must have the object itself as its parent. For example:
class PageOne(tk.Frame):
def __init__(self, parent):
# use the __init__ of the superclass to create the actual frame
tk.Frame.__init__(self, parent)
# all other widgets use self (or some descendant of self)
# as their parent
self.label = tk.Label(self, ...)
self.button = tk.Button(self, ...)
...
Once done, you can treat instances of this class as if they were a single widget:
root = tk.Tk()
page1 = PageOne(root)
page1.pack(fill="both", expand=True)
You can also create a base Page class, and have your actual pages inherit from it, if all of your pages have something in common (for example, a header or footer)
class Page(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
<code common to all pages goes here>
class PageOne(Page):
def __init__(self, parent):
# initialize the parent class
Page.__init__(self, parent)
<code unique to page one goes here>
Your use of OOP is not very logical here. Your main program is in the class GUI_start. If your pages inherit from GUI_start, basically you create a whole new program with every page instance you create. You should instead inherit from Frame as Bryan Oakley has pointed our in the comments. Here is a somewhat repaired version of what you have posted. The original one by Bryan is still much better.
from Tkinter import *
class GUI_Start:
def __init__(self, master):
self.master = master
self.master.geometry('300x300')
self.master.grid_rowconfigure(0, weight=1)
self.master.grid_columnconfigure(0, weight=1)
self.win_colour = '#D2B48C'
self.current_page=0
self.pages = []
for i in range(5):
page = Page(self.master,i+1)
page.grid(row=0,column=0,sticky='nsew')
self.pages.append(page)
for i in range(2):
page = Page_diff(self.master,i+1)
page.grid(row=0,column=0,sticky='nsew')
self.pages.append(page)
self.pages[0].tkraise()
def Next_Page():
next_page_index = self.current_page+1
if next_page_index >= len(self.pages):
next_page_index = 0
print(next_page_index)
self.pages[next_page_index].tkraise()
self.current_page = next_page_index
page1_button = Button(self.master, text='Visit next Page',command = Next_Page)
page1_button.grid(row=1,column=0)
class Page(Frame):
def __init__(self,master,number):
super().__init__(master,bg='#D2B48C')
self.master = master
self.master.tkraise()
page1_label = Label(self, text='PAGE '+str(number))
page1_label.pack(fill=X,expand=True)
class Page_diff(Frame):
def __init__(self,master,number):
super().__init__(master)
self.master = master
self.master.tkraise()
page1_label = Label(self, text='I am different PAGE '+str(number))
page1_label.pack(fill=X)
root = Tk()
gui = GUI_Start(root)
root.mainloop()

Trouble binding events in TKinter

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).

Categories