Switch Between Two Frames in Tkinter

Switch between two frames in tkinter?

One way is to stack the frames on top of each other, then you can simply raise one above the other in the stacking order. The one on top will be the one that is visible. This works best if all the frames are the same size, but with a little work you can get it to work with any sized frames.

Note: for this to work, all of the widgets for a page must have that page (ie: self) or a descendant as a parent (or master, depending on the terminology you prefer).

Here's a bit of a contrived example to show you the general concept:

try:
import tkinter as tk # python 3
from tkinter import font as tkfont # python 3
except ImportError:
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()

start page page 1 page 2

If you find the concept of creating instance in a class confusing, or if different pages need different arguments during construction, you can explicitly call each class separately. The loop serves mainly to illustrate the point that each class is identical.

For example, to create the classes individually you can remove the loop (for F in (StartPage, ...) with this:

self.frames["StartPage"] = StartPage(parent=container, controller=self)
self.frames["PageOne"] = PageOne(parent=container, controller=self)
self.frames["PageTwo"] = PageTwo(parent=container, controller=self)

self.frames["StartPage"].grid(row=0, column=0, sticky="nsew")
self.frames["PageOne"].grid(row=0, column=0, sticky="nsew")
self.frames["PageTwo"].grid(row=0, column=0, sticky="nsew")

Over time people have asked other questions using this code (or an online tutorial that copied this code) as a starting point. You might want to read the answers to these questions:

  • Understanding parent and controller in Tkinter __init__
  • Tkinter! Understanding how to switch frames
  • How to get variable data from a class
  • Calling functions from a Tkinter Frame to another
  • How to access variables from different classes in tkinter?
  • How would I make a method which is run every time a frame is shown in tkinter
  • Tkinter Frame Resize
  • Tkinter have code for pages in separate files
  • Refresh a tkinter frame on button press

How to switch between two tkinter frames

The mistake is in the following lines:

HR.pack(fill='BOTH', expand=1)
Schain.pack(fill='BOTH', expand=1)

You must either directly give the string in lowercase as 'both' or use the pre-defined tkinter constant BOTH. On changing 'BOTH' to BOTH in both the places, your code works properly.



Working Code:

from tkinter import *
root= Tk()
root.title("HOSPITAL MANAGEMENT SYSTEM")
root.geometry('600x350')

#=======frames====
HR=Frame(root)
Schain=Frame(root)

#=========================functions for switching the frames========
def change_to_HR():
HR.pack(fill=BOTH, expand=1)
Schain.pack_forget()

def change_to_Schain():
Schain.pack(fill=BOTH, expand=1)
HR.pack_forget()

#=====================Add heading logo in the frames======
labelHR=Label(HR, text="HR DEPARTMENT")
labelHR.pack(pady=20)

labelSchain=Label(Schain, text="SUPPLY CHAIN DEPARTMENT")
labelSchain.pack(pady=20)
#===============================add button to switch between frames=====
btn1=Button(root, text="HR", command=change_to_HR)
btn1.pack(pady=20)
btn2=Button(root, text="SUPPLY CHAIN", command=change_to_Schain)
btn2.pack(pady=20)

root.mainloop()

How to switch between frames in tkinter

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.

How do I switch frame using place in tkinter?

For the frame switching inside PageOne, you need to hide the current frame before showing the requested frame. Also it is better to create the three color frames in the __init__() and show it in the corresponding function:

class PageOne(tk.Frame):
def __init__(self, parent, controller):
...

# create the three color frames with initially hidden
self.blueFrame = tk.Frame(self, relief=RIDGE, bd=1, bg="blue")
self.redFrame = tk.Frame(self, relief=RIDGE, bd=1, bg="red")
self.yellowFrame = tk.Frame(self, relief=RIDGE, bd=1, bg="yellow")

def blue(self):
self.hide_all_frames()
self.blueFrame.place(x=160, y=0, width=1190, height=720)

def red(self):
self.hide_all_frames()
self.redFrame.place(x=200, y=0, width=1150, height=720)

def yellow(self):
self.hide_all_frames()
self.yellowFrame.place(x=240, y=0, width=1110, height=720)

def hide_all_frames(self, event=None):
self.redFrame.place_forget()
self.blueFrame.place_forget()
self.yellowFrame.place_forget()

If you want to hide all color frames after switching frames, i.e. PageOne -> MainPage -> PageOne, you can notify the PageOne using virtual event when it is raised. Then PageOne hides all the color frames upon receiving such virtual event:

class SampleApp(tk.Tk):
...

def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
# notify the raised frame via virtual event
frame.event_generate('<<Raised>>')

...

class PageOne(tk.Frame):
def __init__(self, parent, controller):
...

self.blueFrame = tk.Frame(self, relief=RIDGE, bd=1, bg="blue")
self.redFrame = tk.Frame(self, relief=RIDGE, bd=1, bg="red")
self.yellowFrame = tk.Frame(self, relief=RIDGE, bd=1, bg="yellow")

self.bind('<<Raised>>', self.hide_all_frames)


Related Topics



Leave a reply



Submit