Search code examples
pythontkinterbackgroundmultiple-inheritancetkinter-layout

How to keep a constant layout in multiple windows Tkinter program?


I am working on a Python Tkinter application which is expected to have multiple windows. At the same time, I would like to keep certain layout (background image, Top/bottom labels) constant. I have tried to set the background image (b_image) and top left label (topleft_label ) but it's not showing up. Can someone look at this snippet and advise how to achieve this?

import tkinter as tk

LARGE_FONT= ("Verdana", 12)
HEIGHT = 768
WIDTH = 1024

class MainApp(tk.Tk):

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

        tk.Tk.__init__(self, *args, **kwargs)
        self.title("Sales System") # set the title of the main window
        self.geometry("%dx%d+0+0" % (WIDTH, HEIGHT)) # set size of the main window to 300x300 pixels

        container = tk.Frame(self)

        b_image = tk.PhotoImage(file='background.png')
        b_label = tk.Label(container, image=b_image)
        b_label.place(relwidth=1, relheight=1)

        topleft_label = tk.Label(container, bg='black', fg='white', text="Welcome - Login Screen", justify='left', anchor="w", font="Verdana 12")
        topleft_label.place(relwidth=0.5, relheight=0.05, relx=0.25, rely=0, anchor='n')

        container.pack(side="top", fill="both", expand = True)

        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames = {}

        frame = StartPage(container, self)

        self.frames[StartPage] = 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="Start Page", font=LARGE_FONT)
        label.pack(pady=10,padx=10)

app = MainApp()
app.mainloop()

Solution

  • The approach

    The best way of going about this is most likely be to make a base_frame class, which contains the image and the topleft_label, "Welcome - Login Screen". This means the StartPage object can inherit the background image from the base_frame class.


    The Code

    import tkinter as tk
    
    LARGE_FONT= ("Verdana", 12)
    HEIGHT = 768
    WIDTH = 1366
    
    
    class MainApp():
        def __init__(self, master):
            self.master = master
            self.master.title("Sales System") 
            self.master.geometry("%dx%d+0+0" % (WIDTH, HEIGHT)) 
    
            self.frames = {}
    
            start_page = StartPage(master)
    
            self.frames[StartPage] = start_page
    
            start_page.grid(row=0, column=0, sticky="nsew")
            self.master.grid_rowconfigure(0, weight=1)
            self.master.grid_columnconfigure(0, weight=1)
    
            self.show_frame(StartPage)
    
        def show_frame(self, cont):
    
            frame = self.frames[cont]
            frame.tkraise()
    
    
    class base_frame(tk.Frame):
        def __init__(self, master, *args, **kwargs):
            tk.Frame.__init__(master, *args, **kwargs)
    
            b_image = tk.PhotoImage(file='background.png')
            b_label = tk.Label(self, image=b_image)
            b_label.image = b_image
            b_label.place(x=0, y=0, relwidth=1, relheight=1)
    
            topleft_label = tk.Label(self, bg='black', fg='white', text="Welcome - Login Screen", justify='left', anchor="w", font="Verdana 12")
            topleft_label.place(relwidth=0.5, relheight=0.05, relx=0.25, rely=0, anchor='n')
    
    class StartPage(base_frame):
    
        def __init__(self, parent):
            super().__init__(self, parent)
            label = tk.Label(self, text="Start Page", font=LARGE_FONT)
            label.pack(pady=10,padx=10)
    
    def main():
        root = tk.Tk() # MainApp()
        main_app = MainApp(root)
        root.mainloop()
    
    if __name__ == '__main__':
        main()
    

    The Breakdown

    Starting the Code

    The piece of code that makes this class system run is like so:

    def main():
        root = tk.Tk() # MainApp()
        main_app = MainApp(root)
        root.mainloop()
    
    if __name__ == '__main__':
        main()
    

    The line if __name__ == '__main__':, in English, roughly translates too: If the program is run and not imported. So, if the program is run and not imported, run the main function.

    root = tk.Tk() simply creates a Tk window inside of the root variable.

    main_app = MainApp(root) initializes the main_app object with its master being the root variable

    root.mainloop() starts the tkinter loop.

    The MainApp Class

    The MainApp Class starts by setting its title to "Sales System" and resetting the geometry to the values defined in HEIGHT & WIDTH:

            self.master = master
            self.master.title("Sales System") 
            self.master.geometry("%dx%d+0+0" % (WIDTH, HEIGHT)) 
    

    Then the self.frames dictionary & the start_page is initialized and the start_page is placed in self.frames:

            self.frames = {}
    
            start_page = StartPage(master)
    
            self.frames[StartPage] = start_page
    

    The start_page is then set to fill the whole of the window:

            start_page.grid(row=0, column=0, sticky="nsew")
            self.master.grid_rowconfigure(0, weight=1)
            self.master.grid_columnconfigure(0, weight=1)
    

    We then show the first page:

    self.show_frame(StartPage)
    

    The show_frame function is then created

        def show_frame(self, cont):
            frame = self.frames[cont]
            frame.tkraise()
    

    The base_frame Class

    The first 3 lines creates a class which takes values the same as a tk.Frame object does, with args and key word args passed through:

    class base_frame(tk.Frame):
        def __init__(self, master, *args, **kwargs):
            tk.Frame.__init__(master, *args, **kwargs)
    

    Then the image label is created:

            b_image = tk.PhotoImage(file='background.png')
            b_label = tk.Label(self, image=b_image)
            b_label.image = b_image
            b_label.place(x=0, y=0, relwidth=1, relheight=1)
    

    The b_label.image = b_image line is used to make sure the image is shown by the label (this is required when loading from within a function).

    We then create the default topleft_label:

    topleft_label = tk.Label(self, bg='black', fg='white', text="Welcome - Login Screen", justify='left', anchor="w", font="Verdana 12")
            topleft_label.place(relwidth=0.5, relheight=0.05, relx=0.25, rely=0, anchor='n')
    

    You may wish to update this code for these labels to be changed in the future, to do this simply replace topleft_label with self.topleft_label and b_label with self.b_label

    The StartPage Class

    This class is not much different to the class you created previously:

    class StartPage(base_frame):
    
        def __init__(self, parent):
            super().__init__(self, parent)
            label = tk.Label(self, text="Start Page", font=LARGE_FONT)
            label.pack(pady=10,padx=10)
    

    The only difference being instead of inheriting from tk.Frame, it inherits from the base_frame class.