How to add background image in Tkinter using OOP concept?

Tags: , , , ,



I am new to object oriented python programming. Currently I am using Python 3. I want to create an app which has multiple pages. I am able to navigate between pages but find it difficult to add image in the background.

Please note I don’t want to know how to background image in tkinter as I am already able to do it based on the following code.

bg = PhotoImage(file="images\bg.png")
label_bgImage = Label(master, image=bg)
label_bgImage.place(x=0, y=0)

I want to know how to add background image to pages when you are defining each window as a class. I put the code to insert background image in the __init__() method of class ABCApp. When I tried to insert the code for adding background image to my existing code, it stopped showing the labels and buttons and now just shows the window.

The following code was my attempt to add an image as background.

import tkinter  as tk
from tkinter import ttk
from tkinter import *

class ABCApp(tk.Tk):

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

        tk.Tk.__init__(self,*args,**kwargs)

        self.geometry("1500x750")

        main_frame = tk.Frame(self)
        main_frame.pack(side = 'top',fill = 'both',expand ='True')

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

        bg = PhotoImage(file="images\bg.png")
        label_bgImage = Label(self, image=bg)
        label_bgImage.place(x=0, y=0)

        self.frames = {}

        for F in (HomePage,PageOne,PageTwo):
            frame = F(main_frame, self)
            self.frames[F] = frame
            frame.grid(row=0,column=0,sticky='nsew')

        self.show_frame(HomePage)

    def show_frame(self,container):

        frame = self.frames[container]
        frame.tkraise()


class HomePage(tk.Frame):

    def __init__(self,parent,controller):

        tk.Frame.__init__(self,parent)

        label = ttk.Label(self,text='Home Page',font =("Helvetica",20))
        label.pack(padx=10,pady=10)

        button1 = ttk.Button(self,text = "Page 1",command = lambda: controller.show_frame(PageOne))
        button1.pack()

        button6 = ttk.Button(self, text="Page 2", command=lambda: controller.show_frame(PageTwo))
        button6.pack()

class PageOne(tk.Frame):

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

        label1 = ttk.Label(self, text='Page 1', font=("Helvetica", 20))
        label1.pack(padx=10, pady=10)

        button2 = ttk.Button(self, text="Back", command=lambda: controller.show_frame(HomePage))
        button2.pack()

        button5 = ttk.Button(self, text="Page 2", command=lambda: controller.show_frame(PageTwo))
        button5.pack()

class PageTwo(tk.Frame):

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

        label2 = ttk.Label(self, text='Page 2', font=("Helvetica", 20))
        label2.pack(padx=10, pady=10)

        button3 = ttk.Button(self, text="Back", command=lambda: controller.show_frame(HomePage))
        button3.pack()

        button4 = ttk.Button(self, text="Page 1", command=lambda: controller.show_frame(PageOne))
        button4.pack()

app = ABCApp()
app.mainloop()

I would like to have the same or different image to appear as background of each page. Either way I am satisfied.

Answer

Since the background image is the same on all the Page classes, an object-oriented way to do it would be to define a base class with the background image on it and then derive all of the concrete Page classes from that instead of a plain tk.Frame. Afterwards, each subclass will need to call its base class’ __init__() method before adding whatever widgets are unique to it. In the code below, this new base class is the one named BasePage.

To avoid loading a separate copy of the image file for each BasePage subclass instance, it’s only done once and saved as an attribute of the ABCApp class (which is the controller argument being passed to the constructor of each BasePage subclass). Because each page class completely covers-up all the others when it’s made visible, each one does need to create its own Label with the background image on it.

Below shows what I mean. (Note I’ve made the code more PEP 8 – Style Guide for Python Code compliant than what’s in your question).

import tkinter as tk
from tkinter.constants import *
from tkinter import ttk


class ABCApp(tk.Tk):
    BKGR_IMAGE_PATH = '8-ball.png'

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.geometry("1500x750")

        main_frame = tk.Frame(self)
        main_frame.pack(side='top', fill='both', expand='True')

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

        self.bkgr_image = tk.PhotoImage(file=self.BKGR_IMAGE_PATH)

        self.frames = {}
        for F in (HomePage, PageOne, PageTwo):
            frame = F(main_frame, self)
            self.frames[F] = frame
            frame.grid(row=0, column=0, sticky='nsew')

        self.show_frame(HomePage)

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


class BasePage(tk.Frame):

    def __init__(self, parent, controller):
        super().__init__(parent)

        label_bkgr = tk.Label(self, image=controller.bkgr_image)
        label_bkgr.place(relx=0.5, rely=0.5, anchor=CENTER)  # Center label w/image.


class HomePage(BasePage):

    def __init__(self, parent, controller):
        super().__init__(parent, controller)

        label = ttk.Label(self, text='Home Page', font =("Helvetica",20))
        label.pack(padx=10, pady=10)

        button1 = ttk.Button(self, text="Page 1",
                             command=lambda: controller.show_frame(PageOne))
        button1.pack()

        button6 = ttk.Button(self, text="Page 2",
                             command=lambda: controller.show_frame(PageTwo))
        button6.pack()


class PageOne(BasePage):

    def __init__(self,parent,controller):
        super().__init__(parent, controller)

        label1 = ttk.Label(self, text='Page 1', font=("Helvetica", 20))
        label1.pack(padx=10, pady=10)

        button2 = ttk.Button(self, text="Back",
                             command=lambda: controller.show_frame(HomePage))
        button2.pack()

        button5 = ttk.Button(self, text="Page 2",
                             command=lambda: controller.show_frame(PageTwo))
        button5.pack()


class PageTwo(BasePage):

    def __init__(self, parent, controller):
        super().__init__(parent, controller)

        label2 = ttk.Label(self, text='Page 2', font=("Helvetica", 20))
        label2.pack(padx=10, pady=10)

        button3 = ttk.Button(self, text="Back",
                             command=lambda: controller.show_frame(HomePage))
        button3.pack()

        button4 = ttk.Button(self, text="Page 1",
                             command=lambda: controller.show_frame(PageOne))
        button4.pack()


app = ABCApp()
app.mainloop()

Also note that if you wanted each Page class to have a different background image, you would do things a little differently — i.e. each page would become responsible for loading its own image. To do that, the call to the BasePage constructor would need to be passed the name of the image file to be used (as an additional argument in the super().__init__() statement).



Source: stackoverflow