Raising a Tkinter frame from a separate function - python

Apologies if the title is a bit broad.
I am creating a Tkinter app with multiple pages and I'm using this piece of code in order to do so.
Each page is a frame and frames are raised by calling the 'show_frame' function. I have no problem with switching between pages using buttons, however I want to run a certain function and change pages if a condition is met.
Below is an example:
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is the start page", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame("PageOne"))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame("PageTwo"))
button1.pack()
button2.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 1", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
def doSomething():
...
if x == y:
"RAISE PAGE ONE"
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
As you can see, the doSomething function is outside of any class. How would I go about raising PageOne from this function.

The easiest answer would be to make doSomething() a method of SampleApp. You would then have access to the method show_frame. However, your question seems to imply this isn't an option.
Failing this, I would advise passing app to the function, and then calling app.show_frame("PageName").
If you don't want to pass a pointer to app as an argument, then you could pass doSomething the function app.show_frame as foo, and then call foo("PageName") when you want the page to be shown.
E.g.
def doSomething(foo):
'''some code'''
foo("page name")
doSomething(app.show_frame)

Related

tkinter automatically loading window

I have this code here and I want to pass values from one class (StartPage), to another (PageOne). I made a method in my controller "get_page" that let me access to page data among classes. So, I have an entry in StartPage and i want to pass its value to PageOne. So, the code runs, but in PageOne there is a print for debug. As soon as i start the program it automatically run all the code, almost like if PageOne is called even if I don't press the button. And the value I want to pass to the second page never gets passed. Can anyone help me out here please?
Thanks in advance!
import tkinter as tk # python 3
from tkinter import font as tkfont
from typing_extensions import IntVar # python 3
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageOne):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
def get_page(self, classname):
'''Returns an instance of a 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
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="Start Page: insert value", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
self.some_input = tk.StringVar()
self.some_entry = tk.Entry(self, width=8)
self.some_entry.pack()
self.some_input = self.some_entry.get()
button1 = tk.Button(self, text='Next Page', command=lambda: controller.show_frame("PageOne"))
button1.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
start_page = self.controller.get_page('StartPage')
value = start_page.some_entry.get()
print ('The value stored in StartPage entry = %s' % start_page.some_input)
label = tk.Label(self, text="This is page 1, the value stored is" + str(value), font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
You need to find a way to notify PageOne to update the label whenever it is shown by show_frame(), one of the way is via virtual event using event_generate() function:
class SampleApp(tk.Tk):
...
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
# notify frame that it is raised via virtual event
frame.event_generate("<<Raised>>")
...
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
# save a reference of 'StartPage'
self.start_page = self.controller.get_page('StartPage')
# use instance variable 'self.label' instead of local variable 'label'
self.label = tk.Label(self, font=controller.title_font)
self.label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
# bind the virtual event
self.bind("<<Raised>>", self.on_raised)
def on_raised(self, event):
self.label.config(text=f"This is page 1, the value stored is '{self.start_page.some_entry.get()}'")

Function to switch between two frames in tkinter

I'm looking through the code at passing-functions-parameters-tkinter-using-lambda, and needed a tad more functionality inside his class PageOne(tk.Frame). Instead of using lambda commands below (as he did):
button1 = tk.Button(self, text="Back to Home",
command=lambda: controller.show_frame(StartPage))`
I'd like to be able to create a function that had an if/then hierarchy inside of it... specifically to check if all other inputs on PageOne had been fulfilled first (which I then know how to do) before allowing a frame change.
If this can be done individually using lambda, even better. Can anyone help me out?
Update: Using Bryan's advice and reformatting for the original code he linked, I now have:
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "APP") #window heading
self.title_font = tkfont.Font(family='Helvetica', size=12) #options: weight="bold",slant="italic"
container = tk.Frame(self) #container = stack of frames; one on top is visible
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):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame #puts all pages in stacked order
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name): #show a frame for the given page name
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is the start page", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame("PageOne"))
button1.pack()
button2.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
####FIX PART 1####
self.next1 = tk.Button(self,text="Next",padx=18,highlightbackground="black",
command=lambda: self.maybe_switch("PageTwo"))
self.next1.grid(row=10,column=1,sticky='E')
####FIX PART 2####
def maybe_switch(self, page_name):
if ###SOMETHING###:
self.controller.show_frame(page_name)
if __name__ == "__main__":
app = App()
app.mainloop()
You shouldn't put any logic in a lambda. Just create a normal function that has any logic you want, and call it from the button. It's really no more complicated that that.
class SomePage(...):
def __init__(...):
...
button1 = tk.Button(self, text="Back to Home",
command=lambda: self.maybe_switch_page(StartPage))
...
def maybe_switch_page(self, destination_page):
if ...:
self.controller.show_frame(destination_page)
else:
...
If you want a more general purpose solution, move the logic to show_frame, and have it call a method on the current page to verify that it is OK to switch.
For example:
class Controller(...):
...
def show_frame(self, destination):
if self.current_page is None or self.current_page.ok_to_switch():
# switch the page
else:
# don't switch the page
Then, it's just a matter of implementing ok_to_switch in every page class.

Edit Window with TKinter

How do I change the geometry of the window and label when I have multiple frames?
Without frames, my code would be:
nGui = Tk()
nGui.geometry("500x500")
But I'm unsure of what 'nGui' is in below code (my whole code as requested). Therefore when this is run it comes up as a very small window. I think it might be 'tk.Tk' but when i tried to edit it, it just made a new window.
class DietBuddy(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageOne, Diet_Finder):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="HazaTea Productions", font=TITLE_FONT)
label.place(relx=.5, rely=.5, anchor="center")
time.sleep(2)
button = tk.Button(self, text="Continue",
command=lambda: controller.show_frame("PageOne"))
button.place(relx=.5, rely=.6, anchor="center")
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 1", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Find Diet",
command=lambda: controller.show_frame("Diet_Finder"))
button.pack()
class Diet_Finder(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="Diet Finder", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Find my Diet!",
command=lambda: controller.show_frame("StartPage"))
button.pack()
if __name__ == "__main__":
app = DietBuddy()
app.mainloop()
Thank you in advance - if this is in the wrong place, please have mercy and tell me I'm new to this site.
DietBuddy class subclassed Tk. So call geometry method against the DietBuddy instance:
if __name__ == "__main__":
app = DietBuddy()
app.geometry('500x500') # <---
app.mainloop()

Tkinter call function in another class

Hi i got some code from an answer on Switch between two frames in tkinter which im trying to modify so a button in the StartPage class can call a function called msg in PageOne class.
But im getting this error:
AttributeError: 'Frame' object has no attribute 'msg'
Here is the code so far i marked out the modifications i made to the code.
import tkinter as tk # python3
#import Tkinter as tk # python
TITLE_FONT = ("Helvetica", 18, "bold")
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.parent = parent #<-- my mod
label = tk.Label(self, text="This is the start page", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame("PageOne"))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame("PageTwo"))
self.button3 = tk.Button(text='Calling msg in another class', command=self.parent.msg)#<-- my mod
button1.pack()
button2.pack()
button3.pack()#<-- my mod
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.parent = parent #<-- my mod
label = tk.Label(self, text="This is page 1", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
def msg(self): #<-- my mod
print("IT WORKS!!")
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 2", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
Could anyone help me out?
Since you're instantiating all your classes in a loop, the easiest way to add references between them is probably to do it after that loop. Remove those references from inside the class, as they won't be valid until after instantiation. Note how button3 is no longer created or packed in StartPage, but rather in SampleApp.
for F in (StartPage, PageOne, PageTwo):
...
self.frames['StartPage'].pageone = self.frames['PageOne']
self.frames['StartPage'].button3 = tk.Button(
text='Calling msg in another class',
command=self.frames['StartPage'].pageone.msg) #<-- my mod
self.frames['StartPage'].button3.pack() #<-- my mod
Try this:
#code to get a page and use it inside any page
#1. create a variable name ex my_pageTwo
#2. show and get frame(page)
#3. now you can use a function from page
my_pageTwo = self.show_frame(PageTwo)
#
my_pageTwo = self.get_page(PageTwo)
#4. example
my_pageTwo.myfunction() #without self
#another example from my tkinter app:
#this function is inside main page:
def open_page8_and_ch_list(self):
if Pages.EEG_raw != '':
page_8 = self.show_frame(PageEight)
page_8 = self.get_page(PageEight)
self.show_frame(PageEight)
page_8.list_initial_ch_names()
else:
self.show_frame(PageEight)
message1_pg8(self)
def message1_pg8(self):
lines = ['Hey! Open a file to edit.']
tkinter.messagebox.showinfo('Myapp', "\n".join(lines))
# On the other hand "open_page8_and_ch_list
#" goes to container1 to open something in page8:
c1_button7 = tk.Button(container1,
text='Edit inicial EEG',
command=lambda: open_page8_and_ch_list(self))
c1_button7.grid(row=0, column=7))
I got stuck with this also, but after some tweaking and grasping the whole width of the code:
self.controller.frames['YourFrameName'].method()
You see everything has been instantiated and stored in a dictionary in the sampleapp class. And every other frame will have reference to the base 'sampleapp' class. So its as simple as referencing the base app and calling the frame that has the method that we want.

TKinter focus command not working on the widget of the second called form from stack of forms [duplicate]

This question already has answers here:
Tkinter focus_set and focus_force not working as expected
(2 answers)
Closed 6 years ago.
I used the codes (below) from Switch between two frames in tkinter
(thanks to Brian Oakley) on my first pilot project using Python3.5 + Tkinter and was having issue doing a "focus" on an Entry widget from the second form (PageOne), the focus is not working at all.
However, if I do the "focus" on any widget from the first or starting form (StartPage) it works! And if I switch calling first PageOne before StartPage the "focus" on the second form would now work. Can you please provide some tips on how I would handle doing a "focus" on any of the widget on the second form (PageOne) based on the code below?
import tkinter as tk
TITLE_FONT = ("Helvetica", 18, "bold")
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is the start page", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame("PageOne"))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame("PageTwo"))
button1.pack()
button2.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 1", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 2", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
I was able to find an answer to this issue, I found it from this post. And the explanation was given by anderswb
What you are doing in the init method of DIS is to draw all of the pages, basically on top of each other, and then you use tkraise to pull the one you need to the front using tkraise(). For some reason this seems to mess up the focus, so you need to set the focus after you've pulled it to the front. That is why I've added an extra method which will be called on all of you pages after it has been raised. – anderswb Oct 20 '15 at 19:27
tkinter-focus-set-and-focus-force-not-working-as-expected

Categories