Search code examples
pythontkintertkinter-layout

How do I switch frame using "place" in tkinter?


I'm new to Tkinter and python. I'm following Switch between two frames in tkinter to see switch frames and it worked. Next, I'm trying to write the code for switch frames inside Page One, but I don't have any idea how to do it.

Below is the code:

from tkinter import *
import tkinter as tk
from tkinter import font as tkfont

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)

        width = 1350  # 1280
        height = 720
        screen_width = self.winfo_screenwidth()
        screen_height = self.winfo_screenheight()
        x = (screen_width / 2) - (width / 2)
        y = (screen_height / 2) - (height / 2)
        self.geometry(f'{width}x{height}+{int(x)}+{int(y)}')
        self.resizable(False,False)

        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()


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"))         
        button1.pack()
        


class PageOne(tk.Frame):

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

        LeftFrame=tk.Frame(self,relief=RIDGE, bd=2)
        LeftFrame.place(x=0,y=0,width=160,height=720)

        button = tk.Button(LeftFrame, text="Go to the start page",font=("bold"),command=lambda: controller.show_frame("StartPage")) 
        button.grid(row=0)

        buttonBlue = tk.Button(LeftFrame, text="Go to the blue page",bg="blue",fg="white",command=self.blue,activebackground="blue",activeforeground="white")                   
        buttonBlue.grid(row=1)

        buttonRed = tk.Button(LeftFrame, text="Go to the red page",bg="red",fg="white",command=self.red,activebackground="red",activeforeground="white")                  
        buttonRed.grid(row=2)

        buttonYellow = tk.Button(LeftFrame, text="Go to the yellow page",bg="yellow",fg="black",command=self.yellow,activebackground="yellow",activeforeground="black")
        buttonYellow.grid(row=3)

    def blue(self):
        # self.hide_all_frames()
        blueFrame=tk.Frame(self,relief=RIDGE,bd=1 ,bg="blue")
        blueFrame.place(x=160,y=0,width=1190,height=720)

    def red(self):  # Do I need to put self here? It still worked without putting self here
        # self.hide_all_frames()
        redFrame=tk.Frame(self,relief=RIDGE,bd=1 ,bg="red")
        redFrame.place(x=200,y=0,width=1150,height=720)

    def yellow(self):
        # self.hide_all_frames()
        yellowFrame=tk.Frame(self,relief=RIDGE,bd=1 ,bg="yellow")
        yellowFrame.place(x=240,y=0,width=1110,height=720)

    def hide_all_frames(self):
        self.blue.withdraw()


if __name__ == "__main__":
    app = SampleApp()
    app.mainloop()

When I switch to Page One, I create a couple of buttons to switch color frames. They all overlap every time I switch between colors like the image below. overlap frames

I'm finding a way to switch color frames without overlapping each other. And, when I go back to the Start Page, all color frames should be destroyed/hidden. Please help me. Thank you so much.


Solution

  • 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)