Understanding root and tk.Frame in tkinter - python

The example below has previously been used to describe the functionality of classes and switching pages in Tkinter.
Although I don't understand:
Why classes PageOne and PageTwo need to inherit from tk.Frame?
Where the equivilant of root = Tk() is (since in other beginner tutorials I watched, this step is essential).
Code:
import tkinter as tk # python 3
from tkinter import font as tkfont # python 3
#import Tkinter as tk # python 2
#import tkFont as tkfont # python 2
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()
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=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()

Why classes PageOne and PageTwo need to inherit from tk.Frame?
They don't have to inherit from tk.Frame. Because they contain other widgets, tk.Frame is the natural choice. You could use tk.Canvas or literally any other widget (though, using widgets like tk.Button or tk.Scrollbar make no sense)
Where the equivilant of root = Tk()
It is this line of code:
app = SampleApp()
SampleApp inherits from tk.Tk, so it behaves exactly the same. You're free to rename app to root if you wish.

Related

How to open another window and close the current window in GUI?

I'm new to Graphic User Interface using Python. I was able to open the register page after clicking the Register button from the login page.
Below is the code files:
login.py
from tkinter import *
from tkinter import ttk
from register import Register
class Login:
def __init__(self):
self.loginw = Tk()
self.loginw.title("Login")
self.loginw.geometry("500x500")
self.signin = Button(self.loginw,width=20, text="Register", command=self.register)
self.signin.place(relx=0.5, rely=0.5, anchor=CENTER)
def register(self):
win = Toplevel()
Register(win)
w=Login()
w.loginw.mainloop()
register.py
from tkinter import *
from tkinter import ttk
class Register:
def __init__(self, win):
self.reg = win
self.reg.title("Register")
self.reg.geometry("500x500")
self.revert = Button(self.reg,width=20, text="Return to Login")
self.revert.place(relx=0.5, rely=0.5, anchor=CENTER)
self.reg.mainloop()
Is there a way to write the code like:
After clicking the register button from the login page, the register page pops up and the login page disappears.
After clicking the Return to Login button from the register page, the register page disappears, and the login page comes back.
Thank you so much.
Taken from another stackoverflow question:
import tkinter as tk # python 3
from tkinter import font as tkfont # 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, 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()
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=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()
In your case class StartPage could be your register page and PageOne your Login page. take that piece of code as a base to start.
Switch between two frames in tkinter
In this case the SampleApp class acts like the master frame (is a container) for other frames. So there is no pop up windows.
Another base template for tkinter app. Taken from the same link:
import tkinter as tk
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self._frame = None
self.switch_frame(Register)
def switch_frame(self, frame_class):
"""Destroys current frame and replaces it with a new one."""
new_frame = frame_class(self)
if self._frame is not None:
self._frame.destroy()
self._frame = new_frame
self.geometry('925x600+'+self.screen()+'+20')
self._frame.pack()
def screen(self):
screen_width = self.winfo_screenwidth()
posX = (screen_width //2) - (925//2)
return str(posX)
class Register(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
tk.Label(self, text="This is the register Page").pack(side="top", fill="x", pady=10)
tk.Button(self, text="Login",
command=lambda: master.switch_frame(Login)).pack()
class Login(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
tk.Label(self, text="Login Page").pack(side="top", fill="x", pady=10)
self.usuario = tk.Entry(self)
self.usuario.insert(-1, 'User')
self.usuario.config(foreground='gray')
self.usuario.pack(side="top", fill="x", padx=10, ipady=3)
self.password = tk.Entry(self)
self.password.insert(-1, "Password")
self.password.config(foreground='gray')
self.password.pack(side="top", fill="x", pady=10, padx=10, ipady=3)
tk.Button(self, text="Return to register",
command=lambda: master.switch_frame(Register)).pack()
tk.Button(self, text="Login",
command=lambda: print("not implemented yet")).pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
edit: to answer another question:
To change the title (in the first example) you must call inside one of these classes StartPage, PageOne, PageTwo
controller.title("the title you want")
That is because in this part of code (in the class SampleApp, the main class) you are passing itself as second parameter.
self.frames = {}
for F in (StartPage, PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self) //controller=self
self.frames[page_name] = frame
In the main class (SampleApp) you would do:
self.title("Some title")
In the second example:
master.title("Login") //inside Login or Register
self.title("Something") //inside SampleApp

tkinter passing data among classes

I was reading this tutorial of StackOverflow on how to get variables among classes, but I'm not able to make it work. Basically what i want to do is to pass an entry value from StartPage to PageOne where i need it. I also tried "the wrong way" using global variables, but I need an int and I can't covert the string entry to an int. Down below there's my code, can you help me please?
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.valore = tk.IntVar()
entry = tk.Entry(self, textvariable=self.valore).pack()
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
start_page = self.controller.get_page("StartPage")
value = start_page.entry.get()
print ('The value stored in StartPage entry = %s' % value)
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()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()

Open new frame from Menu (new window in already opened window)

I am using a part of code to open different pages inside one window and it works fine. When I am trying to open a window from menu (file > New Test), it opens a new window instead of the same window. I am really confused with this and have a couple of questions.
How to open Window from Menu in the same Window.
Why menubar is visible in a page1 (NewTestWindow) if it is defined in the MainPage.
Is it a correct way to make a separate class for menubar or it is better to insert it in the class MainWindow.
import tkinter as tk
from tkinter import font as tkfont
class MenuBar(tk.Menu):
def __init__(self, parent, controller):
tk.Menu.__init__(self, controller)
self.controller = controller
fileMenu = tk.Menu(self, tearoff=0)
self.add_cascade(label="File", underline=0, menu=fileMenu)
fileMenu.add_command(label="New Test", underline=1, command=lambda: SampleApp().show_frame("NewTestWindow"))
fileMenu.add_separator()
fileMenu.add_command(label="Exit", underline=2, command=self.onexit)
def onexit(self):
quit()
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")
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 (MainWindow, NewTestWindow):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("MainWindow")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class MainWindow(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.menubar = MenuBar(self, parent)
self.controller.config(menu=self.menubar)
label = tk.Label(self, text="This is the main page", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Start new test",
command=lambda: controller.show_frame("NewTestWindow"))
button1.pack()
class NewTestWindow(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.grid(row=1, column=0)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("MainWindow"))
button.grid(row=2, column=0)
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
change this:
command=lambda: SampleApp().show_frame("NewTestWindow")
to this:
command=lambda: self.show_frame(NewTestWindow)

MVC tkinter model. Switch between frames: add new one on the run

I am following the MVC model that Brian made on the answer to “Switch between two frames in tkinter“. He stacks the frames on top of each other (all are made at the very beginning) and then we just show them at our will.
I try to add another frame on the run. I know it is made (because you get an error before creating it and no error after), but I am cannot show it. What am I missing?
Thanks
import tkinter as tk # python 3
from tkinter import font as tkfont # python 3
#import Tkinter as tk # python 2
#import tkFont as tkfont # python 2
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()
def add_new(self):
''' Create a new frame on the run '''
self.frames["PageNew"] = PageNew(parent=tk.Frame(self), controller=self)
self.frames["PageNew"].grid(row=0, column=0, sticky="nsew")
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"))
button3 = tk.Button(self, text="Go to a New Page ",
command=lambda: controller.show_frame("PageNew"))
button1.pack()
button2.pack()
button3.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="Create a new page and go to the start page",
command=self.on_click)
button.pack()
def on_click(self):
self.controller.add_new()
self.controller.show_frame("StartPage")
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=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button2 = tk.Button(self, text="Go to the new page",
command=lambda: controller.show_frame("PageNew"))
button1.pack()
button2.pack()
class PageNew(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is the new page", 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()
Your problem is the parent of your PageNew:
self.frames["PageNew"] = PageNew(parent=tk.Frame(self), controller=self)
Its parent is a Frame you create, but it is not packed inside the GUI so you won't see it, nor its child PageNew. In order for PageNew to be like the other pages, you need to give it the same parent, which is container here.
Since you will need container outside of __init__, you need to make it an attribute of SampleApp, i.e. replace container = tk.Frame(self) by self.container = tk.Frame(self).
And now, in add_new, you can create your PageNew with
self.frames["PageNew"] = PageNew(parent=self.container, controller=self)

Change the size of the GUI window tkinter

I've tried to change the window size for this GUI but i'm struggling. I tried to use root.geometry("1080x800+200+200") but that doesnt seem to work as well. Can someone explain why? i'm currently just practicing using tkinter. thanks
import tkinter as tk # python3
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()
You do not have a root declared in your program so you will not be able to call root.geometry. If you change your code to something like this then you will be able to call root.geometry and change the size of your GUI window, also by using root you can pass it in as a parameter to your other page classes and set different sizes for them all if you wish.
if __name__ == "__main__":
root = tk.Tk()
root.geometry("1080x800+200+200")
app = SampleApp(root)
root.mainloop()
In first class SampleApp you can just use self.geometry('500x555') #for example.

Categories

Resources