I am developping an application with tkinter. It is made of several frame (windows) put one onto the other. I can pass from one window to the other with a push button. I built it upon an example I saw here on Stack overflow, but I cannot recall the link to it.
I suspect that due to this choice I made, the tkinter does not close properly after I call the method self.quit().
The example below is a simplified version of my app, although it has the same structure than the full app.
With the code below, you can see that the application won't close before the end of the full program, i.e. after the 10 sec delay I put.
import tkinter as tk
import tkinter.ttk as ttk
import time
class App:
"""
Inherited from the Frame class
"""
def __init__(self):
self.root = tk.Tk()
self.root.title('Test')
# This frame will contain all windows
container = ttk.Frame(self.root)
container.pack(side="top", fill="both", expand=True)
# Create different pages/windows of the application
self.frames = {}
self.frames["Window1"] = Window1(parent=container, controller=self)
self.frames["Window2"] = Window2(parent=container, controller=self)
self.frames["Window3"] = Window3(parent=container, controller=self)
self.frames["Window1"].grid(row=0, column=0, sticky="nsew")
self.frames["Window2"].grid(row=0, column=0, sticky="nsew")
self.frames["Window3"].grid(row=0, column=0, sticky="nsew")
self.show_frame("Window1")
def run(self):
self.root.deiconify
self.root.mainloop()
def show_frame(self, page_name):
'''
Show a frame for the given page name
'''
frame = self.frames[page_name]
frame.tkraise()
class Window1(ttk.Frame):
def __init__(self, parent, controller):
ttk.Frame.__init__(self, parent)
self.controller = controller
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
# Go to window 2
self.buttonW2 = ttk.Button(self, text='Go to 2', command=lambda: controller.show_frame("Window2"))
self.buttonW2.grid(row=0, column=0, sticky='NSE')
# Go to window 3
self.buttonW3 = ttk.Button(self, text='Go to 3', command=lambda: controller.show_frame("Window3"))
self.buttonW3.grid(row=0, column=1, sticky='NSE')
# EXIT button
self.buttonExit = ttk.Button(self, text='Exit', command=self.quit)
self.buttonExit.grid(row=0, column=2, sticky='NSE')
class Window2(ttk.Frame):
def __init__(self, parent, controller):
ttk.Frame.__init__(self, parent)
self.controller = controller
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
# Go back button
buttonBack = ttk.Button(self, text="Back to 1", command=lambda: controller.show_frame("Window1"))
buttonBack.grid(row=0, column=3, sticky="NSE")
class Window3(ttk.Frame):
def __init__(self, parent, controller):
ttk.Frame.__init__(self, parent)
self.controller = controller
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
# Go back button
buttonBack = ttk.Button(self, text="Back to 1", command=lambda: controller.show_frame("Window1"))
buttonBack.grid(row=1, column=1, sticky="E")
if __name__ == '__main__':
app = App()
app.run()
print('The App should be closed')
time.sleep(10)
print('The App is now closed')
Does anyone can help me for this?
Thanks
Tkinters quit command just stops the mainloop but doesn't destroy the window. If you want the window to be removed use destroy.
So create the exitbutton like:
self.buttonExit = ttk.Button(self, text='Exit', command=self.controller.root.destroy)
self.buttonExit.grid(row=0, column=2, sticky='NSE')
Related
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
I have a tkinter app in which I have a main canvas with multiple pages (all of which are frames). I pull up the different pages by rasing them with the frame.tkraise() command. I now want to add a scrollbar to the whole thing. The scrollbar appears but without a slider and I am not sure if it cna recognize the change of page.
import tkinter as tk
from tkinter import ttk
class Economics(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
tk.Tk.columnconfigure(self, 0, weight=1)
tk.Tk.rowconfigure(self, 0, weight=1)
self.container = tk.Canvas()
self.container.grid(row=0, column=0, sticky="nsew")
self.container.columnconfigure("all", weight=1)
self.container.rowconfigure("all", weight=1)
self.vscrollbar = tk.Scrollbar(orient="vertical", command=self.container.yview)
self.container.configure(yscrollcomman=self.vscrollbar.set)
self.vscrollbar.grid(row=0, column=1, sticky="ns")
self.frames = {}
for F in (StartPage, ExamplePage1, ExamplePage2): # TUPLE OF PAGES
frame = F(self.container, self)
self.frames[F] = frame
self.show_frame(StartPage)
def show_frame(self, cont):
self.container.delete("all")
frame = self.frames[cont]
self.container.create_window(0, 0, anchor="nw", window=frame)
class StartPage(tk.Frame):
def __init__(self, parent, controller):
super().__init__(parent)
button_1 = ttk.Button(self, text="Example Page 1",
command=lambda: controller.show_frame(ExamplePage1))
button_1.grid(row=0, column=0)
button_2 = ttk.Button(self, text="Example Page 2",
command=lambda: controller.show_frame(ExamplePage2))
button_2.grid(row=1, column=0)
class ExamplePage1(tk.Frame):
def __init__(self, parent, controller):
super().__init__(parent)
for i in range(50):
label = tk.Label(self, text="Button {} of 50".format(i+1))
label.grid(row=i, column=0)
button_back = ttk.Button(self, text="Back",
command=lambda: controller.show_frame(StartPage))
button_back.grid(row=0, column=1)
class ExamplePage2(tk.Frame):
def __init__(self, parent, controller):
super().__init__(parent)
for i in range(35):
label = tk.Label(self, text="Button {} of 35".format(i+1))
label.grid(row=i, column=0)
button_back = ttk.Button(self, text="Back",
command=lambda: controller.show_frame(StartPage))
button_back.grid(row=0, column=1)
app = Economics()
app.geometry("800x600")
app.resizable(True, True)
app.mainloop()
In this example file you can see the basic structure of my app with some example widgets and buttons. The scrollbar shows but without the slider. What do I have to change to get a working scrollbar for all pages.
Later on I'm planning to get a horizontal scrollbar as well.
You can't scroll items added to a canvas with pack, place, or grid. A canvas can only scroll items added via the canvas create_* functions, such as create_window.
I am trying to make a tkinter widow with multiple frames, but also the functions of notebook, like multiple widows. The problem is I am kind of unfamiliar with tkinter and am not sure how to do that. This is my current code, and it doesn't work, and would love to know what I should do to make it work. Again, the dream end result would be that I would have a first widow, which says the test text, and then the 2nd window which has multiple tabs.
from tkinter import ttk
import tkinter as tk
Font= ("Verdana", 8)
LargeFont = ("Verdana", 12)
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)
self.frames = {}
for F in (StartPage, PageOne):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
label = tk.Label(self, text="Info", font=LargeFont)
label.pack(pady=2,padx=10)
text = tk.Label(self, text="testtestestetetqwegfegeg\ntestwegwegwegweg", font=Font)
text.pack(pady=2,padx=2)
button = tk.Button(self, text="Go to the Card",
command=lambda: controller.show_frame(PageOne))
button.pack(fill="x")
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
nb = ttk.Notebook(ttk.Frame())
nb.grid(row=1, column=0, columnspan = 50, rowspan=49, sticky='nesw')
p1 = (nb)
nb.add(p1, text='test')
label = tk.Label(self, text="", font=LargeFont)
label.pack(pady=10,padx=10)
button1 = tk.Button(self, text="Back to Home",
command=lambda: controller.show_frame(StartPage))
button1.pack()
app = App()
app.mainloop()
The error that I eventually get is that it creates a third frame that is displayed with the test tab. Everything else works.
Thanks for your help
I know exactly what you mean because I'm trying the same. To me
nb = ttk.Notebook(self)
worked.
best
Pkanda
Taubate Brazil
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)
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.