I am writing a Python program with a GUI and have selected tkinter to do so. I have some experience with Python, but I am new to tkinter and GUI programming.
I have been reading a few tutorials about how to use tkinter. What I could find was very basic, such as "how to display a button". This leaves me struggling to identify a useful model for structuring the part of my program that defines the UI.
So far my searches only yielded 1 guide for structuring a python/tkinter GUI in an OOP style: pythonprogramming.net
Although this is a welcome example and very helpful in its specificity, it seems to me as though the inheritance of tkinter classes and adding new unrelated code to those new classes violates a strict separation of concerns. It looks very convenient on the short term, but I can't tell if it has undesirable consequences long-term.
As an alternative I created another example, in which I made similar classes, but avoided inheriting from tkinter classes, by composing various tkinter objects. This keeps functionality separated with only a couple of additional methods.
I would appreciate feedback on which approach is more useful as a UI grows in complexity. This could include specific suggestions on other models to use, links to information on the subject, source code examples of programs using tkinter, and so on.
Example of inheritance based on pythonprogramming.net:
import tkinter as tk
class AppMain(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 = {}
frame = Page(container, self)
self.frames[Page] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(Page)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class Page(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Start Page", font=("Verdana", 12))
label.pack(padx=10, pady=10)
def main():
application = AppMain()
application.mainloop()
if __name__ == "__main__":
main()
Alternative without inheritance:
EDIT 1: Add grid variable to Page init
import tkinter as tk
class AppMain(object):
def __init__(self):
self.root = tk.Tk()
container = tk.Frame(self.root)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.pages = {}
page = Page(container, self.root, {"row": 0, "column": 0, "sticky": "nsew"})
self.pages[Page] = page
self.show_page(Page)
def show_page(self, container):
page = self.pages[container]
page.show()
def run(self):
self.root.mainloop()
class Page(object):
def __init__(self, parent, controller, grid):
self.frame = tk.Frame(parent)
self.frame.grid(**grid)
label = tk.Label(self.frame, text="Start Page", font=("Verdana", 12))
label.pack(padx=10, pady=10)
def show(self):
self.frame.tkraise()
def main():
application = AppMain()
application.run()
if __name__ == "__main__":
main()
The main advantage of inheriting from a tkinter widget (typically a Frame) is so that you can treat that object like any other widget when it comes to laying out your UI.
For example, a typical UI might be made of a toolbar, a side panel for navigation, a main work area, and perhaps a statusbar at the bottom. By creating a class for each of these which inherits from Frame, you can lay out your GUI like so:
toolbar = Toolbar(root)
sidebar = Sidebar(root)
main = WorkArea(root)
statusbar = Statusbar(root)
toolbar.pack(side="top", fill="x")
statusbar.pack(side="bottom", fill="x")
sidebar.pack(side="left", fill="y")
main.pack(side="right", fill="both", expand=True)
If you use composition instead of inheritance, then either your main program needs to know something about the internal structure of your objects, or your objects need to know something about the root window.
For example, you might have to name your inner frame of each section with a common name so that the main program can lay it out:
toolbar.inner_frame.pack(side="top", fill="x")
statusbar.inner_frame.pack(side="bottom", fill="x")
sidebar.inner_frame.pack(side="left", fill="y")
main.inner_frame.pack(side="right", fill="both", expand=True)
In the example in your question where you inherit from object, your Page class has to know that the root window is using grid, and further has to know that it need to place itself in a specific row and column. This tightly couples these two parts of your code together -- you can't modify one part of your code without having to modify other parts.
For example, let's say you have a dozen pages. After working on the code for a while you decide that AppMain needs to place an additional widget in row zero of the container. You now have to go in and modify all dozen page classes so that they can place themselves in row 1 instead of row 0.
Related
I would like to create a contractible panel in a GUI, using the Python package tkinter.
My idea is to create a decorator for the tkinter.Frameclass, adding a nested frame and a "vertical button" which toggles the nested frame.
Sketch: (Edit: The gray box should say Parent of contractible panel)
I got it to toggle just fine, using the nested frame's grid_remove to hide it and then move the button to the left column (otherwise occupied by the frame).
Now I want to be able to use it like any other tkinter.Frame, but let it target the nested frame. Almost acting like a proxy for the nested frame. For example, adding a tkinter.Label (the green Child component in the sketch) to the decorator should add the label to the nested frame component (light yellow tk.Frame in the sketch) not the decorator itself (strong yellow ContractiblePanel in the sketch).
Minimal example: (omitting the toggling stuff and any "formatting"):
(Here's a published (runnable) Repl project)
import tkinter
class ContractiblePanel(tkinter.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self._panel = tkinter.Frame(self)
self._toggle = tkinter.Button(self, text='<', command=self._toggle_panel)
self.grid(row=0, column=0, sticky='nsw')
self._panel.grid(row=0, column=0, sticky='nsw')
self._toggle.grid(row=0, column=1, sticky='nsw')
def _toggle_panel(self):
# ...
if __name__ == '__main__':
root = tkinter.Tk()
root.geometry('128x128')
contractible_panel = ContractiblePanel(root)
Forwarding configuration calls is just overriding the config method I guess?
class ContractiblePanel(tkinter.Frame):
# ...
def config(self, **kwargs):
self._panel.config(**kwargs)
# ...
contractible_panel.config(background='blue')
But I would like to be able to add a child component into the nested panel frame by
label_in_panel = tkinter.Label(contractible_panel, text='yadayada')
How do I get the ContractiblePanel object to act like a proxy to its member _panel, when adding child components?
What other methods/use cases should I consider? I am quite new to tkinter and thus expect the current implementation to break some common practices when developing tkinter GUIs.
This is an interesting question. Unfortunately, tkinter really isn't designed to support what you want. I think it would be less complicated to simply expose the inner frame and add widgets to it.
That being said, I'll present one possible solution. It's not implemented as a python decorator, but rather a custom class.
The difficulty is that you want the instance of the custom class to represent the outer frame in one context (for example, when packing it in your UI) and the inner frame in another context (when adding child widgets to it)
The following solution solves this by making the instance be the inner frame, and then overriding pack,place, and grid so that they operates on the outer frame. This works fine, with an important exception: you cannot use this class directly inside a notebook or embedded in a text widget or canvas.
I've used colors and borders so it's easy to see the individual components, but you can remove the colors in production code, obviously. Also, I used a label instead of a button since I created the screenshot on OSX where the background color of a button can't be changed.
import tkinter as tk
class ContractiblePanel(tk.Frame):
def __init__(self, parent, **kwargs):
self._frame = tk.Frame(parent, **kwargs)
super().__init__(self._frame, bd=2, relief="solid", bg="#EFE4B0")
self._button = tk.Label(
self._frame, text="<", bg="#00A2E8", bd=2,
relief="solid", font=("Helvetica", 20), width=4
)
self._frame.grid_rowconfigure(0, weight=1)
self._frame.grid_columnconfigure(0, weight=1)
self._button.grid(row=0, column=1, sticky="ns", padx=4, pady=4)
super().grid(row=0, column=0, sticky="nsew", padx=4, pady=4)
self._button.bind("<1>", lambda event: self.toggle())
def collapse(self):
super().grid_remove()
self._button.configure(text=">")
def expand(self):
super().grid()
self._button.configure(text="<")
def toggle(self):
self.collapse() if self.winfo_viewable() else self.expand()
def pack(self, **kwargs):
# override to call pack in the private frame
self._frame.pack(**kwargs)
def grid(self, **kwargs):
# override to call grid in the private frame
self._frame.grid(**kwargs)
def place(self, **kwargs):
# override to call place in the private frame
self._frame.place(**kwargs)
root = tk.Tk()
root.geometry("400x300")
cp = ContractiblePanel(root, bg="yellow", bd=2, relief="raised")
cp.pack(side="left", fill="y", padx=10, pady=10)
label = tk.Label(cp, text="Child component", background="#22B14C", height=3, bd=2, relief="solid")
label.pack(side="top", expand=True, padx=20, pady=20)
root.mainloop()
First of all it is kinda gross to use this code and it's very confusing. So I'm really not sure if you really want to take this route. However, it is possible to achieve it.
The basic idea is to have a wrapper and to pretend the wrapper is the actual object you can lie with __str__ and __repr__ about what the class really is. That is not what a proxy means.
class WrapperClass:
def __init__(self, master=None, **kwargs):
self._wrapped_frame = tk.Frame(master, **kwargs)
self._panel = tk.Frame(self._wrapped_frame)
self._toggle = tk.Button(self._wrapped_frame, text='<', command=self._toggle_panel)
self._wrapped_frame.grid(row=0, column=0, sticky='nsw')
self._panel.grid(row=0, column=0, sticky='nsw')
self._toggle.grid(row=0, column=1, sticky='nsw')
return None
def _toggle_panel(self):
print('toggle')
def __str__(self):
return self._panel._w
__repr__ = __str__
You can do even more confusing things by delegate the lookup-chain to the _wrapped_frame inside the WrapperClass this enables you to call on the instance of WrapperFrame() methods like pack or every other method. It kinda works similar for inheritance with the difference that by referring to the object, you will point to different one.
I don't recommend using this code by the way.
import tkinter as tk
NONE = object()
#use an object here that there will no mistake
class WrapperClass:
def __init__(self, master=None, **kwargs):
self._wrapped_frame = tk.Frame(master, **kwargs)
self._panel = tk.Frame(self._wrapped_frame)
self._toggle = tk.Button(self._wrapped_frame, text='<', command=self._toggle_panel)
self._wrapped_frame.grid(row=0, column=0, sticky='nsw')
self._panel.grid(row=0, column=0, sticky='nsw')
self._toggle.grid(row=0, column=1, sticky='nsw')
return None
def _toggle_panel(self):
print('toggle')
def __str__(self):
return self._panel._w
__repr__ = __str__
def __getattr__(self, name):
#when wrapper class has no attr name
#delegate the lookup chain to self.frame
inreturn = getattr(self._wrapped_frame, name, NONE)
if inreturn is NONE:
super().__getattribute__(name)
return inreturn
root = tk.Tk()
wrapped_frame = WrapperClass(root, bg='red', width=200, height=200)
root.mainloop()
I am having a problem Disabling Menu's in my Tkinter App. Only I want to show them only on certain pages, I have tried to disable them in the init function of my app, but that didn't work, I have tried to disable them in the show frames function of my app but that didn't work and I have tried to disable them through the start page class of my app, in that I have tried to use self.menubar , parent.menubar and controler.menubar; But nothing seems to work. I would just code them on each individual page but this is the only way I found to even show them on any page. Any help would be much appreciated.
class App(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)
menubar = tk.Menu(container)
file = tk.Menu(menubar, tearoff = 0)
file.add_command(label='Exit', command = quit)
menubar.add_cascade(label='File',menu=file)
tk.Tk.config(self, menu=menubar)
self.frames = {}
for F in (StartPage):
frame = F(container,self)
self.frames[F] = frame
frame.grid(row=0,column=0,sticky='nsew')
page = F
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)
menubar.add_cascade(label='File',state=DISABLED)
label = ttk.Label(self, text='Start', font = LARGE_FONT).pack()
main = App()
main.mainloop()
This isn't a tkinter problem, it's how python works - to modify an object you need a reference to the object. This is true for tkinter widgets just as it is true for dictionaries, strings, or any other object in python.
In this case the object is part of the main application. You first need to save a reference to it:
class App(tk.Tk):
def __init__(self,*args,**kwargs):
...
self.menubar = tk.Menu(container)
...
In later code, you can now access this menu from the controller variable, which is a reference to the instance of App:
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
controller.menubar.entryconfigure('File',state="disabled")
However, that likely won't do what you want since that code runs when the program starts up. I'm guessing you want the code to run when the page is selected. To do that, follow the instructions in this answer to the question How would I make a method which is run every time a frame is shown in tkinter
1st lets clean up this to reflect the PEP8 standard more closely for readability reason.
Added in the imports as those should have been in your example.
We need to replace the parenthesis in (StartPage) with brackets like this [StartPage]. This is because a single value in parenthesis is treated like a single value without parenthesis so you are trying to iterate over an object and not a list.
You should be more specific on what you are configuring. Instead of calling tk.tk.config() do self.config(menu=self.menubar).
for your StartPage class you are not going to be able to do anything to the menubar for several reason. First you do not define the menubar as a class attribute in your main tk class. 2nd you are not properly calling your master and container so I have changed the argument names in the init to better reflect what we are working with so you can see what you need to call.
Lastly the crux if the issue for disabling the menu you need to use entryconfig()
See below cleaned up code:
import tkinter as tk
import tkinter.ttk as ttk
class App(tk.Tk):
def __init__(self):
super().__init__()
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.menubar = tk.Menu(container)
self.config(menu=self.menubar)
file = tk.Menu(self.menubar, tearoff=0)
file.add_command(label='Exit', command=quit)
self.menubar.add_cascade(label='File', menu=file)
self.frames = {}
for F in [StartPage]:
frame = F(self, container)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky='nsew')
page = F
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, master, container):
tk.Frame.__init__(self, container)
master.menubar.entryconfig('File', state='disabled')
ttk.Label(self, text='Start').pack()
main = App()
main.mainloop()
Currently resisting the temptation to throw my laptop out of the window and smash it with a bat at this point.
Currently, I'm trying to create a simple GUI for what used to be a nice simple text based RPG game. But trying to work with a GUI makes me want to die.
I just want to have a scaleable way to swap between frames in the game. (Currently there exists the main menu and the Work in progress character creation screen because I can't even manage to get even just that to work.)
I've tried most things that I can find on this website and on discord servers and I seem to just get a new error every time.
I just want to know how to swap between these since trying anything that I can find online just creates more errors.
There are more "screens" to come since it's a game so a scaleable solution would be perfect thanks.
import tkinter
from tkinter import *
from tkinter import ttk
from PIL import ImageTk, Image
root = Tk()
content = ttk.Frame(root)
root.geometry("600x600")
class CharacterCreate(tkinter.Frame):
def __init__(self, parent):
tkinter.Frame.__init__(self)
self.parent = parent
backgroundchar = ImageTk.PhotoImage(Image.open("plont2.png"))
backgroundlabelchar = tkinter.Label(content, image = backgroundchar)
backgroundlabelchar.image = backgroundchar
backgroundlabelchar.grid(row=1,column=1)
Charname = tkinter.Label(content, text = "Enter your character name here:").grid(row=0)
e1 = tkinter.Entry(content)
e1.grid(row=0, column=1)
e1.lift()
CharBtn1 = Button(content, text="Return to main menu", width = 15, height = 1)
CharBtn1.grid(row=2, column=2)
CharBtn1.lift()
class MainMenu(tkinter.Frame):
def __init__(self, parent):
tkinter.Frame.__init__(self)
self.parent = parent
background = ImageTk.PhotoImage(Image.open("bred.png"))
content.grid(column=1, row=1)
Btn1 = Button(content, text="Play", width=5, height=1, command = CharacterCreate.lift(1))
Btn2 = Button(content, text="Quit", width=5, height=1, command = root.quit)
backgroundlabel = tkinter.Label(content, image=background)
backgroundlabel.image = background
backgroundlabel.grid(row=1, column=1)
Btn1.grid(row=1, column=1, padx=(50), pady=(50))
Btn1.columnconfigure(1, weight=1)
Btn1.rowconfigure(1, weight=1)
Btn1.lift()
Btn2.grid(row=1, column=2, padx=(50), pady=(50))
Btn2.columnconfigure(2, weight=1)
Btn2.rowconfigure(1, weight=1)
Btn2.lift()
MainMenu(1)
root.mainloop()
You have five major problems:
you are calling a function immediately (command=CharacterCreate.lift(1)) rather than at the time the button is clicked (command=CharacterCreate.lift),
you are passing an invalid argument to lift - you are passing 1, but the argument to lift must be another widget,
you are calling lift on a class rather than an instance of a class.
you never create an instance of CharacterCreate
your classes inherit from Frame but you never use the classes as frames -- they each place their widgets directly in container
Switching between pages usually involves one of two techniques: create all the frames at startup and then lift the active frame above the others, or destroy the current frame and recreate the active frame. You seem to be attempting to do the latter, so this answer will show you how to do that.
Since fixing your program is going to require many changes, I am instead going to show you a template that you can use to start over.
Let's start with an import, and then the definition of your pages. To keep the example short, each class will have a single label so that you can distinguish between them (note: importing tkinter "as tk" is done simply to make the code a bit easier to read and type):
import tkinter as tk
class CharacterCreate(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="I am CharacterCreate")
label.pack(padx=20, pady=20)
class MainMenu(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="I am MainMenu")
label.pack(padx=20, pady=20)
Your original code created a container, so we'll do that next. We need to create the root window, too:
root = tk.Tk()
container = tk.Frame(root)
container.pack(fill="both", expand=True)
Now we need to create an instance of each page, giving them the container as the parent. As a rule of thumb, the code that creates a widget should be the code that calls pack, place, or grid on the widget, so we have to do that too. We need to make sure that grid is configured to give all weight to row 0 column 0.
main = MainMenu(container)
cc = CharacterCreate(container)
main.grid(row=0, column=0, sticky="nsew")
cc.grid(row=0, column=0, sticky="nsew")
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
We need a way to lift one of the classes above the other. That's best handled by a function. To make the code easier to understand we'll save the pages in a dictionary so we can reference them by name. This name will be the argument to the function.
pages = {"cc": cc, "main": main}
def switch(name):
page = pages[name]
page.lift()
Finally, we need to start with the main menu on top, and we need to start the event loop:
switch('main')
root.mainloop()
With that, you have a program that runs and displays the main menu. To finish the example lets add a button to the menu to switch to the create page, and create a button in the create page to switch back to the menu.
First, inside the __init__ of MainMenu add the following after the code that creates the label. Notice that because we need to pass an argument to switch, we use lambda:
button = tk.Button(self, text="Go to creater", command=lambda: switch('cc'))
button.pack()
And next, inside the __init__ of CharacterCreate add the following after the code that creates the label:
button = tk.Button(self, text="Go to main menu", command=lambda: switch('main'))
button.pack()
With that, you now have the basic structure to create as many pages as you want, and easily switch to them by name.
I am currently writing a tkinter GUI app using Python 3. I have several frames in my application which should have the same navigation bar(some button widgets and some Label widgets packed in a LabelFrame widget). I want to write this code in a function and call it instead of writing it to every frame and having to change every frame every time I change something in the navigation bar.
I tried putting all of the widgets in a function and calling it just as I said above but it didn't work because I wrote every frame as a class and when I call the function, it doesn't understand where I want to put the widgets to.I pass self as the first argument for creating the widget to place it in the main frame but when I call it in a function, it can't find what self is.
I am looking for any possible way to "teach" the function self or another way to write the navigation bar only once and make any modifications there.
(I think writing the tkinter GUI by writing frames as class has a specific name but I can't recall it right now)
EDIT: Based on a comment here is a example of what I want to do;
(Example below is written without considering the fact it is not possible to use pack with grid. Any other improvements to the code are welcome.)
from tkinter import ttk
import tkinter as tk
#lots of other imports here, they are not related to the question.
def nav_bar():
navLabelFrame=tk.LabelFrame(self)
button=ttk.Button(navLabelFrame,text="I am a button",
command=lambda:controller.show_frame(Page1))
#Other buttons and labels
#...
class app(tk.Tk):
def __init__(self,*args,**kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "Hello World")#TITLE OF THE WINDOW
container = tk.Frame(self)
container.pack(side="top", fill="both", expand = False)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in:(Page1,Page2,Page3,
Page4,Page5,Page6):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(UserLogin)#SET HOME PAGE
def show_frame(self, cont):#FUNCTION TO SWITCH PAGES
frame = self.frames[cont]
frame.tkraise()
class Page1(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
Page1.grid(self)
nav_bar()#place the contents of the navbar
#place other stuff here
class Page2(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
Page1.grid(self)
nav_bar()#place the contents of the navbar
#place other stuff here
class Page3(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
Page1.grid(self)
nav_bar()#place the contents of the navbar
#place other stuff here
GUI=app()
GUI.mainloop()
Im trying to teach my self how to use tkinter and I found a useful code through youtube that I don't really fully understand. Would appreciate it if some could help me understand it. Marked the things I did not understand with # ... **.
import tkinter as tk # why not from tkinter import? **
class SampleApp(tk.Tk): # why 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(container, 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()
import tkinter as tk # why not from tkinter import? **
Why not from tkinter import *? Because that is the wrong way to do it. Global imports are bad. Tkinter tutorials tend to do it the wrong way for some reason that I don't understand.
The reason for the as tk part is so that you can do tk.Frame rather than tkinter.Frame, making the code a little easier to type and a little easier to read. It's completely optional.
class SampleApp(tk.Tk):
tk is the name of the tkinter module that was imported. Tk is the name of a class in that module that represents the root window. Every tkinter application must have a single root window. By placing it inside of SampleApp, this creates a subclass of this widget -- a copy that has additional features.
It's not necessary to inherit from tk.Tk. You can inherit from tk.Frame, any other tkinter widget, or even object. It's a personal preference. The choice makes some things easier, some things harder.
container = tk.Frame(self)
The above creates an instance of a Frame widget, which will be used as a container for other "pages". These "pages" will all be stacked on top of each other in this container.
frame = F(container, self)
F is the loop variable. The loop is iterating over a list of classes, so each time through the loop F will be representing a class. F(...) creates an instance of the class. These classes (StartPage, PageOne, PageTwo) all require two parameters: a widget that will be the parent of this class, and an object that will server as a controller (a term borrowed from the UI patter model/view/controller).
The line of code creates an instance of the class (which itself is a subclass of a Frame widget), and temporarily assigns the frame to the local variable frame.
By passing self as the second ("controller") parameter, these new class instances will be able to call methods in the SampleApp class object.
This saves a reference to the just-created frame in a dictionary. The key to the dictionary is the page name (or more accurately, the name of the class).
This is how the show_frame method can determine the actual page widget just from the name of the class.
Creating the frames in a loop is functionally equivalent to the following:
f1 = StartPage(container, self)
f2 = PageOne(container, self)
f3 = PageTwo(container, self)
f1.grid(row=0, column=0, sticky="nsew")
f2.grid(row=0, column=0, sticky="nsew")
f3.grid(row=0, column=0, sticky="nsew")
self.frames = {"StartPage": f1, "PageOne": f2, "PageTwo": f3}
frame.tkraise()
In nearly all GUI toolkits -- tkinter included -- there is the notion of a "stacking order": the order in which things are stacked. Some toolkits might call this the z-order. If two or more widgets are stacked on top of each other (which this code does by putting all pages in the same row and column), the widget that is on the top of the stack is the widget that will typically be visible.
tkraise is a method of a Frame object that will raise the frame to the top of the stacking order. In this line of code, frame refers to one particular instance of one of the pages.
tk.Frame.__init__(self, parent)
Because each page is a subclass of a tk.Frame class, this calls the constructor of the parent class. This is necessary to initialize all of the internal structures that make up the actual frame widget. Although a Frame can take many options, this code chooses to send in only one -- a reference to another widget which is to act as the parent of this new widget.
self.controller = controller
The above code is simply "remembering" the value of the controller variable which was passed in. In this case, the controller is the application. By saving it off, this class can call methods on the SampleApp object.
Note: the code in the question came from a tutorial that copied code from this answer: https://stackoverflow.com/a/7557028/7432. I am the author of that original code, but not the author of the tutorial. In that original answer are links to other questions related to this code.