Switch between two frames in tkinter in separates files - python

I want to modify the code from: Switch between two frames in tkinter. I put the three clases in three separates files but when I call master.switch_frame(master.StartPage) from pageOne.py that give the error :
return getattr(self.tk, attr)
AttributeError: '_tkinter.tkapp' object has no attribute 'StartPage'
Can someone help me with this error? I appreciate any suggestion.
the code is:
main.py
#take from: https://stackoverflow.com/questions/7546050/switch-between-two-frames-in-tkinter
# Multi-frame tkinter application v2.3
import tkinter as tk
import pageOne as p1
import pageTwo as p2
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self._frame = None
self.switch_frame(StartPage)
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._frame.pack()
class StartPage(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
tk.Label(self, text="This is the start page").pack(side="top", fill="x", pady=10)
tk.Button(self, text="Open page one",
command=lambda: master.switch_frame(p1.PageOne)).pack()
tk.Button(self, text="Open page two",
command=lambda: master.switch_frame(p2.PageTwo)).pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
pageOne.py
# Multi-frame tkinter application v2.3
import tkinter as tk
class PageOne(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
tk.Label(self, text="This is page one").pack(side="top", fill="x", pady=10)
tk.Button(self, text="Return to start page",
command=lambda: master.switch_frame(master.StartPage)).pack()
if __name__ == "__main__":
app = PageOne()
app.mainloop()# Multi-frame tkinter application v2.3
import tkinter as tk
class PageOne(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
tk.Label(self, text="This is page one").pack(side="top", fill="x", pady=10)
tk.Button(self, text="Return to start page",
command=lambda: master.switch_frame(master.StartPage)).pack()
if __name__ == "__main__":
app = PageOne()
app.mainloop()
enter code here
pageTwo.py
# Multi-frame tkinter application v2.3
import tkinter as tk
class PageTwo(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
tk.Label(self, text="This is page two").pack(side="top", fill="x", pady=10)
tk.Button(self, text="Return to start page",
command=lambda: master.switch_frame(master.StartPage)).pack()
if __name__ == "__main__":
app = PageTwo()
app.mainloop()
Thank you Bryan Oakley. finally the code that work is:
main.py
#take from: https://stackoverflow.com/questions/7546050/switch-between-two-frames-in-tkinter
# Multi-frame tkinter application v2.3
import tkinter as tk
from StartPage import StartPage
from pageOne import PageOne
from pageTwo import PageTwo
pages = {
"StartPage": StartPage,
"PageOne": PageOne,
"PageTwo": PageTwo
}
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self._frame = None
self.switch_frame("StartPage")
def switch_frame(self, page_name):
"""Destroys current frame and replaces it with a new one."""
cls = pages[page_name]
new_frame = cls(master = self)
if self._frame is not None:
self._frame.destroy()
self._frame = new_frame
self._frame.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
pageOne.py
# Multi-frame tkinter application v2.3
import tkinter as tk
class PageOne(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
tk.Label(self, text="This is page one").pack(side="top", fill="x", pady=10)
tk.Button(self, text="Return to start page", command=lambda: master.switch_frame("StartPage")).pack()
if __name__ == "__main__":
app = PageOne()
app.mainloop()
pageTwo.py
# Multi-frame tkinter application v2.3
import tkinter as tk
class PageTwo(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
tk.Label(self, text="This is page two").pack(side="top", fill="x", pady=10)
tk.Button(self, text="Return to start page", command=lambda: master.switch_frame("StartPage")).pack()
if __name__ == "__main__":
app = PageTwo()
app.mainloop()
StartPage.py
# Multi-frame tkinter application v2.3
import tkinter as tk
class StartPage(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
tk.Label(self, text="This is the start page").pack(side="top", fill="x", pady=10)
tk.Button(self, text="Open page one", command=lambda: master.switch_frame("PageOne")).pack()
tk.Button(self, text="Open page two", command=lambda: master.switch_frame("PageTwo")).pack()

I recommend passing a string as the argument to show_frame. That way, other files don't need to import all of the other classes. You can then create a mapping to be used by switch_frame. That file becomes the only file that needs to import the other pages.
It should look something like the following code. In this example I moved StartPage into a separate file for consistency:
from startPage import StartPage
from pageOne import PageOne
from pageTwo import PageTwo
pages = {
"StartPage": StartPage,
"PageOne": PageOne,
"PageTwo": PageTwo
}
class SampleApp(tk.Tk):
...
def switch_frame(self, page_name):
"""Destroys current frame and replaces it with a new one."""
cls = pages[page_name]
new_frame = cls(master=self)
...
Your other pages don't need to import the pages, they just need to use the page name:
class PageTwo(tk.Frame):
def __init__(self, master):
...
tk.Button(..., command=lambda: master.switch_frame("StartPage")).pack()

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

Understanding root and tk.Frame in tkinter

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.

Edit/add Tkinter widget in one Tkinter class from another Tkinter class

Suppose I have two tkinter classes which act as separate windows. How could I edit any given widget from one class in the other tkinter class. ALso, how could I add a widget in one tkinter class from the other tkinter class?
from tkinter import Tk, Label, Button
class MyFirstGUI:
def __init__(self, master):
self.master = master
master.title("A simple GUI")
self.label = Label(master, text="This is
our first GUI!")
self.label.pack()
self.greet_button = Button(master,
text="Greet", command=self.greet)
self.greet_button.pack()
self.close_button = Button(master,
text="Close", command=master.quit)
self.close_button.pack()
def greet(self):
print("Greetings!")
root = Tk()
my_gui = MyFirstGUI(root)
root.mainloop()
from tkinter import Tk, Label, Button
class MyFirstGUI2:
def __init__(self, master):
self.master = master
master.title("A simple GUI")
self.label = Label(master, text="This is
our first GUI!")
self.label.pack()
self.greet_button = Button(master,
text="Greet", command=self.greet)
self.greet_button.pack()
self.close_button = Button(master,
text="Close", command=master.quit)
self.close_button.pack()
def greet(self):
print("Greetings!")
root = Tk()
my_gui = MyFirstGUI2(root)
root.mainloop()
I think it would be better to use a Toplevel widget for your two windows (or at least one of them). Right now your first window will be created and the code will stop when it gets to the root.mainloop() line. The second window will not be created until you close the first one.
And you can pass in a reference from each class.
import tkinter
from tkinter import Tk, Label, Toplevel, Button
class MainWidget:
def __init__(self, master):
self.master = master
self.widgetTwo = None
self.label = Label(self.master, text='Widget One')
self.label.pack()
class WidgetTwo(Toplevel):
def __init__(self, master, mainWidget):
Toplevel.__init__(self, master)
self.master = master
self.mainWidget = mainWidget
self.labelTwo = Label(self, text='Widget Two')
self.labelTwo.pack()
Button(self, text='Change Main Widget Text', command=self.ChangeMainWidgetLabel).pack()
def ChangeMainWidgetLabel(self):
self.mainWidget.label.config(text='Widget One text changed')
mw = Tk()
mainWidget = MainWidget(mw)
widgetTwo = WidgetTwo(mw, mainWidget)
mainWidget.widgetTwo = widgetTwo
mw.mainloop()

Tkinter Multiple GUI

Hey I am having a problem during connecting two Gui together as when button pressed i want the tkinter to open another gui from another file.
File one contains the frontend dashboard and when you click on the teacher than click on login it gives me an error saying.
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\AppData\Local\Programs\Python\Python37-32\lib\tkinter\__init__.py", line 1705, in __call__
return self.func(*args)
TypeError: __init__() missing 1 required positional argument: 'master'
this is the main page Source Code:
from tkinter import *
from login import *
import tkinter as tk
from tkinter import ttk
class Frontend(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title('Portal') # set the title of the main window
self.geometry('300x300') # set size of the main window to 300x300 pixels
# this container contains all the pages
container = tk.Frame(self)
container.pack(side='top', fill='both', expand=True)
container.grid_rowconfigure(0, weight=1) # make the cell in grid cover the entire window
container.grid_columnconfigure(0,weight=1) # make the cell in grid cover the entire window
self.frames = {} # these are pages we want to navigate to
for F in (Dashboard, Teacher, Student): # for each page
frame = F(container, self) # create the page
self.frames[F] = frame # store into frames
frame.grid(row=0, column=0, sticky='nsew') # grid it to container
self.show_frame(Dashboard) # let the first page is Dashboard
def show_frame(self, name):
frame = self.frames[name]
frame.tkraise()
class Dashboard(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
label = ttk.Label(self, text="Main Screen", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button = ttk.Button(self, text="Teacher",
command=lambda: controller.show_frame(Teacher))
button.pack()
button1 = ttk.Button(self, text="Student",
command=lambda: controller.show_frame(Student))
button1.pack()
class Teacher(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = ttk.Label(self, text="Teacher Login", font=LARGE_FONT)
label.pack(pady=10,padx=10)
#logo = ImageTk.PhotoImage(Image.open('logo.png'))
button = ttk.Button(self, text="Login", command=LoginFrame)
button.pack()
button1 = ttk.Button(self, text="Home",
command=lambda: controller.show_frame(Dashboard))
button1.pack()
class Student(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = ttk.Label(self, text="Student Login", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button = ttk.Button(self, text="Home",
command=lambda: controller.show_frame(Dashboard))
button.pack()
def main():
root = Frontend()
root.mainloop()
if __name__=='__main__':
main()
Login source code:
from tkinter import *
import tkinter.messagebox as tm
class LoginFrame(Frame):
def __init__(self, master):
super().__init__(master)
self.label_username = Label(self, text="Username")
self.label_password = Label(self, text="Password")
self.entry_username = Entry(self)
self.entry_password = Entry(self, show="*")
self.label_username.grid(row=0, sticky=E)
self.label_password.grid(row=1, sticky=E)
self.entry_username.grid(row=0, column=1)
self.entry_password.grid(row=1, column=1)
self.checkbox = Checkbutton(self, text="Keep me logged in")
self.checkbox.grid(columnspan=2)
self.logbtn = Button(self, text="Login", command=self._login_btn_clicked)
self.logbtn.grid(columnspan=2)
self.pack()
def _login_btn_clicked(self):
# print("Clicked")
username = self.entry_username.get()
password = self.entry_password.get()
# print(username, password)
if username == "admin" and password == "admin123":
tm.showinfo("Login info", "Welcome Admin")
else:
tm.showerror("Login error", "Incorrect username")
def main():
root = Tk()
page = LoginFrame(root)
root.mainloop()
if __name__=='__main__':
main()
Thanks

Mysterious extra space after buttons in tkinter

I'm running tkinter on Python3.4 on Windows and I want two buttons in my GUI box.
I'm following [this link]
The code is this:
import tkinter as tk
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.initialize()
def initialize(self):
button_crop = tk.Button(self, text=u"Crop", command=self.OnCrop)
button_crop.pack(side="left")
button_reset = tk.Button(self, text=u"Reset", command=self.OnReset)
button_reset.pack(side="left")
def OnCrop(self):
pass
def OnReset(self):
pass
app = App()
app.mainloop()
Now I get a button which has some extra space to the right
I've tried initialising a grid() and then button_crop.grid(column=0, row=1) but I get the same result.
Please help me remove this extra blank space to the right.
Do you want this behaviour?
import tkinter as tk
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.initialize()
def initialize(self):
button_crop = tk.Button(self, text=u"Crop", command=self.OnCrop)
button_crop.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.E, tk.W))
button_crop = tk.Button(self, text=u"Reset", command=self.OnReset)
button_crop.grid(row=0, column=1, sticky=(tk.N, tk.S, tk.E, tk.W))
for i in range(2):
self.columnconfigure(i, weight=1)
self.rowconfigure(0, weight=1)
def OnCrop(self):
pass
def OnReset(self):
pass
app = App()
app.mainloop()

Categories

Resources