How to switch between different tkinter canvases from a start-up page and return back to start-up page in sub-canvas

Viewed 848

I have created a start-up canvas, which contains buttoms to shfit to two other sub-canvases. In addition, in those two sub-canvases, buttom to return to start-up canvas is created. However, after I enter the sub-canvas, I fail to return to start-up canvas. That is to say, when I click the buttom to return to the start-up canvas, it will create a start-up canvas beside the sub-canvas instead of closing the subcanvas and shifting to the start-up canvas. Is there any way to switch between canvases and return to the main canvas? Thank you!

import tkinter as tk
from tkinter import ttk
import re


class SampleApp(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self._Canvas= None
        self.switch_Canvas(StartUpPage)

    def switch_Canvas(self, Canvas_class):
        new_Canvas = Canvas_class(self)
        if self._Canvas is not None:
                self._Canvas.destroy()
        self._Canvas = new_Canvas
        self._Canvas.pack()

class StartUpPage(tk.Canvas):
    def __init__(self, master, *args, **kwargs):
        tk.Canvas.__init__(self, master, *args, **kwargs)
        tk.Frame(self)
        tk.Label(self, text="Example").grid(column = 0, row = 0)
        tk.Button(self, text="Canvas1",
              command=lambda: master.switch_Canvas(PageOne)).grid(column = 0, row = 1)
        tk.Button(self, text="Canvas2",
              command=lambda: master.switch_Canvas(PageTwo)).grid(column = 0, row = 2)


class PageOne(tk.Canvas, tk.Tk):
    def __init__(self, master, *args, **kwargs):
        root = tk.Canvas.__init__(self, *args, **kwargs)
        self.frame1  = tk.Frame(root, width=430)
        self.frame1.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
        tk.Label(self.frame1, text="First Canvas").pack(side="top", fill="x", pady=5)
        tk.Button(self.frame1, text="Back to start-up page",
              command=lambda: master.switch_Canvas(StartUpPage)).pack()

class PageTwo(tk.Canvas, tk.Tk):
    def __init__(self, master, *args, **kwargs):
        root = tk.Canvas.__init__(self, *args, **kwargs)
        self.frame2  = tk.Frame(root, width=430)
        self.frame2.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
        tk.Label(self.frame2, text="Second Canvas").pack(side="top", fill="x", pady=5)
        tk.Button(self.frame2, text="Back to start-up page",
              command=lambda: master.switch_Canvas(StartUpPage)).pack()


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

So, few things:

First: There's the wrong parent/master relationship in PageOne and PageTwo in

def __init__(self, master, *args, **kwargs):

Since you're inheriting from tk.Canvas, the new instance of the class has parent master which in itself get's passed from

SampleApp's switchCanvas as Canvas_class(self) meaning master is . or the (main/root) window.

But then (we're still inside PageOne) you're making root = tk.Canvas.__init__(self, *args, **kwargs) which becomes None since initializers return None.

Then you're creating a new Frame whose parent is root which is None.

In short

  • PageOne and PageTwo are (also) new instances of Canvas whose parent is the main window, or the instance made by SampleApp.
  • root aka the Frame's parent is None (Which I even don't know how that affects it when you position it byself.frame1.pack(fill=tk.BOTH, side=tk.LEFT, expand=True))

Hence I assume you're trying to making PageOne and PageTwo instances of Frame and put a Canvas inside them.

These are the few changes I made to the two classes: Their instances are now both of (type) tk.Frame that contains the other widgets.

class PageTwo(tk.Frame): # Sub-lcassing tk.Frame
    def __init__(self, master, *args, **kwargs):

        # self is now an istance of tk.Frame
        tk.Frame.__init__(self,master, *args, **kwargs)

        # make a new Canvas whose parent is self.
        self.canvas = tk.Canvas(self,bg='yellow', width=430)
        self.label = tk.Label(self, text="Second Canvas").pack(side="top", fill="x", pady=5) 
        self.button = tk.Button(self, text="Back to start-up page",
              command=lambda: master.switch_Canvas(StartUpPage))
        
        self.button.pack()

        # pack the canvas inside the self (frame).
        self.canvas.pack(fill=tk.BOTH, side=tk.LEFT, expand=True) 

        #print('is instance',isinstance(self,tk.Frame))

Second: Keeping track of which instances/objects are created, instead of continuously spawning new ones. I decided to keep it simple and create an internal dictionary whose keys are the classes of StratUpPage,PageOne or PageTwo meaning each class gets to have one instance that can be switched to.

def switch_Canvas(self, Canvas_class):

        # Unless the dictionary is empty, hide the current Frame (_mainCanvas is a frame)
        if self._mainCanvas:
            self._mainCanvas.pack_forget()

        # Modification 2: is the Class type passed is a one we have seen before?
        canvas = self._allCanvases.get(Canvas_class, False)

        # if Canvas_class is a new class type, canvas is False
        if not canvas:
            # Instantiate the new class
            canvas = Canvas_class(self)
            # Store it's type in the dictionary
            self._allCanvases[Canvas_class] = canvas

        # Pack the canvas or self._mainCanvas (these are all frames)
        canvas.pack(pady = 60)
        # and make it the 'default' or current one.
        self._mainCanvas = canvas

Entire Code

import tkinter as tk
from tkinter import ttk
import re

class SampleApp(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self._mainCanvas= None
        # The dictionary to hold the class type to switch to
        # Each new class passed here, will only have instance or object associated with it (i.e the result of the Key)
        self._allCanvases = dict()
        # Switch (and create) the single instance of StartUpPage
        self.switch_Canvas(StartUpPage)

    def switch_Canvas(self, Canvas_class):

        # Unless the dictionary is empty, hide the current Frame (_mainCanvas is a frame)
        if self._mainCanvas:
            self._mainCanvas.pack_forget()

        # is the Class type passed one we have seen before?
        canvas = self._allCanvases.get(Canvas_class, False)

        # if Canvas_class is a new class type, canvas is False
        if not canvas:
            # Instantiate the new class
            canvas = Canvas_class(self)
            # Store it's type in the dictionary
            self._allCanvases[Canvas_class] = canvas

        # Pack the canvas or self._mainCanvas (these are all frames)
        canvas.pack(pady = 60)
        # and make it the 'default' or current one.
        self._mainCanvas = canvas

class StartUpPage(tk.Canvas):
    def __init__(self, master, *args, **kwargs):
        tk.Canvas.__init__(self, master, *args, **kwargs)
        tk.Frame(self) # Here the parent of the frame is the self instance of type tk.Canvas
        tk.Label(self, text="Example").grid(column = 0, row = 0)
        tk.Button(self, text="Canvas1",
              command=lambda: master.switch_Canvas(PageOne)).grid(column = 0, row = 1)
        tk.Button(self, text="Canvas2",
              command=lambda: master.switch_Canvas(PageTwo)).grid(column = 0, row = 2)


class PageOne(tk.Frame):
    def __init__(self, master, *args, **kwargs):
        tk.Frame.__init__(self,master, *args, **kwargs)
        self.canvas = tk.Canvas(self,bg='blue', width=430)
        print('got',self,master,args,kwargs)
        tk.Label(self, text="First Canvas").pack(side="top", fill="x", pady=5)
        tk.Button(self, text="Back to start-up page",
              command=lambda: master.switch_Canvas(StartUpPage)).pack()

        self.canvas.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)

class PageTwo(tk.Frame): # Sub-lcassing tk.Frame
    def __init__(self, master, *args, **kwargs):
        # self is now an istance of tk.Frame
        tk.Frame.__init__(self,master, *args, **kwargs)
        # make a new Canvas whose parent is self.
        self.canvas = tk.Canvas(self,bg='yellow', width=430)
        self.label = tk.Label(self, text="Second Canvas").pack(side="top", fill="x", pady=5)
        self.button = tk.Button(self, text="Back to start-up page",
              command=lambda: master.switch_Canvas(StartUpPage))

        self.button.pack()
        # pack the canvas inside the self (frame).
        self.canvas.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
        #print('is instance',isinstance(self,tk.Frame))

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

EDIT: Visual Demo

demo

Related