0

I am new to tkinter and encounter this strange behavior with the images. Please pay attention to the *.mainloop() in the code below.


import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk

class BaseWindow(tk.Tk):
    def __init__(self):
        super(BaseWindow, self).__init__()
        self.geometry("800x700")
        self.title("test title")

        frame = ttk.Frame(self)
        frame.pack()
        ttk.Label(frame, text="test label").pack()
        img = ImageTk.PhotoImage(Image.open("res/male_avatar.png").resize(
            (200, 200)))
        ttk.Label(frame, image=img).pack()

        frame_parent = ttk.Frame(self)
        frame_parent.pack()

        ParentWindow(frame_parent, self)
        # self.mainloop()


class ParentWindow(ttk.Frame):
    def __init__(self, parent, app):
        super(ParentWindow, self).__init__(parent)
        self.pack()
        ttk.Label(self, text="parent label test").pack()

        img = ImageTk.PhotoImage(Image.open("res/male_avatar.png").resize(
            (200, 200)))
        ttk.Label(self, image=img).pack()
        ttk.Label(self, text="test label").pack()

        frame_child = ttk.Frame(self)
        frame_child.pack()

        ChildWindow(frame_child, app)
        # app.mainloop()


class ChildWindow(ttk.Frame):
    def __init__(self, parent, app):
        super(ChildWindow, self).__init__(parent)
        self.pack()
        ttk.Label(self, text="child label test").pack()

        img = ImageTk.PhotoImage(Image.open("res/male_avatar.png").resize(
            (200, 200)))
        ttk.Label(self, image=img).pack()
        ttk.Label(self, text="test label").pack()
        # app.mainloop()


if __name__ == '__main__':
    # BaseWindow().mainloop()
    BaseWindow()
    

There are three classes, but the image is created by each class is shown only if the mainloop() is called in the respective class.

All the other widgets work just fine regardless of where I call the mainloop() for instance the Label() widget as in example code below.

Only the images do not display if I don't call the mainloop() in the proper way.

Explaination

1.) if name == .............. in this block if I call the BaseWindow().mainloop() then everything works fine, all the widgets are displayed, but the images are not displayed. Images created in all the classes are not shown.

2.) class BaseWindow()....... if the self.mainloop() is called here then, one image i.e. image created in this class is shown/displayed and other images are not displayed.

3.) class ParentWindow().......... if the app.mainloop() is called here then, two images are displayed, i.e. the image created in the BaseWindow class and ParentWindow class are displayed.

4.) similarly the image in the ChildWindow() class is only displayed if the app.mainloop() is called in this class.

So, in order to display all the images, I need to call the mainloop in the last class, but in this way, I need to pass the app object to all the child classes. Isn't there a way to call mainloop only once in the app and get everything work.? How do I display the images by calling mainloop() only in the BaseWindow() class...?

Chrishdev
  • 55
  • 7
  • 2
    Does this answer your question? [Why does Tkinter image not show up if created in a function?](https://stackoverflow.com/questions/16424091/why-does-tkinter-image-not-show-up-if-created-in-a-function) The `.mainloop()` doesn't allow the function to exit so the `PhotoImage` isn't garbage collected. – TheLizzard Nov 20 '22 at 17:36
  • @TheLizzard ttk.Label() too are just the local variable like img, so why only the labels are displayed but not the image. Aren't the other widgets garbage collected? all the local variable are destroyed right? – Chrishdev Nov 21 '22 at 01:45
  • Widgets do not require an ongoing Python reference, because the underlying Tcl/Tk object will be deleted at an appropriate time. Images, on the other hand, are never automatically deleted at the Tcl/Tk level, and can be megabytes in size. If the Python side didn't auto-delete them when the last reference goes away, there would be no possibility of anything deleting them later; every image would be an unavoidable memory leak. – jasonharper Nov 21 '22 at 04:41

3 Answers3

0

I beginner at this stuff but I think you do need all of those .mainloop() because it updates the UI. But I am not sure. Hope you get more answers!

Code Freak
  • 13
  • 6
  • I agree that you need a `mainloop`, but tthe question is why doesn't it have to be in the class for it to work – TheLizzard Nov 20 '22 at 18:26
  • @TheLizzard I need a working solution in terms of oop, just calling mainloop() at the end of all the methods isn't nice, I wish to call it only once and get stuff working. Is there any solution that fits with oop concepts...? – Chrishdev Nov 21 '22 at 01:57
0

Here i have some thing for you. You can add a single line in child Window class and your problem will be solved.

class ChildWindow(ttk.Frame):
def __init__(self, parent, app):
    super(ChildWindow, self).__init__(parent)
    self.pack()
    ttk.Label(self, text="child label test").pack()

    img = ImageTk.PhotoImage(Image.open("COMSATS.png").resize((200, 200)))
    ttk.Label(self, image=img).pack()
    ttk.Label(self, text="test label").pack()
    self.mainloop()

Add self.mainloop() at the end of last class you have to visit.

self.mainloop()

Then,

if __name__ == '__main__':
BaseWindow()

All the images will be displayed at once.

  • This doesn't have any explanation behind why you put the `parent.mainloop()` in the class and not outside like the commented line: `BaseWindow().mainloop()`. – TheLizzard Nov 20 '22 at 18:27
  • I understand your point and i gonna research about this. According to my view all the case here revolve around the OOP (Object Oriented Programming) concept. In OOP we create a parent class with a child class inherited to parent one. In main function we only create the object of Child class because it inherit all the functionalities of parent class. So i think this concept also applies here too. – Hammad Rafique Nov 20 '22 at 18:53
  • we call a base class and than at the end of last class which is the child class we add `child.mainloop()`, as i say above it act like creating an object of only child class. Here it only loop continuously the child class. No need to add `parent.mainloop()` – Hammad Rafique Nov 20 '22 at 18:59
  • hope so you understand my point. – Hammad Rafique Nov 20 '22 at 19:04
  • This problem is super common, and doesn't have anything to do with functional vs OOP programming. Look at my comment on the question. – TheLizzard Nov 20 '22 at 19:13
  • For that to display all the images, adding self.mainloop() or app.mainloop() in only in the last Class i.e. ChildWindow() is enough, no need to add mainloop() to each class. But the problem is the code here is just the demo for my problem actually I have the same issue in my university project, If I follow your approach, the mainloop() blocks the code execution and every lines added after the mainloop() won't execute. – Chrishdev Nov 21 '22 at 01:52
  • for instance, in BaseWindow() class if I move the mainloop() statement just above 5 lines, than nothing will work. – Chrishdev Nov 21 '22 at 01:56
  • yes, your right, i recommend you to add self before making a local variable of image (Concept of garbage collection). – Hammad Rafique Nov 21 '22 at 08:19
0

I figured out a way. The following solutions works fine. But I don't know if its the correct way. Correct me if I'm wrong...

import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk

class BaseWindow(tk.Tk):
    def __init__(self):
        super(BaseWindow, self).__init__()
        self.geometry("800x700")
        self.title("test title")

        frame = ttk.Frame(self)
        frame.pack()
        ttk.Label(frame, text="test label").pack()
        self.__img = ImageTk.PhotoImage(Image.open("res/male_avatar.png").resize(
            (200, 200)))
        ttk.Label(frame, image=self.__img).pack()

        frame_parent = ttk.Frame(self)
        frame_parent.pack()

        ParentWindow(frame_parent, self)


class ParentWindow(ttk.Frame):
    def __init__(self, parent, app):
        super(ParentWindow, self).__init__(parent)
        self.pack()
        ttk.Label(self, text="parent label test").pack()

        self.__img = ImageTk.PhotoImage(Image.open("res/male_avatar.png").resize(
            (200, 200)))
        ttk.Label(self, image=self.__img).pack()
        ttk.Label(self, text="test label").pack()

        frame_child = ttk.Frame(self)
        frame_child.pack()

        ChildWindow(frame_child, app)


class ChildWindow(ttk.Frame):
    def __init__(self, parent, app):
        super(ChildWindow, self).__init__(parent)
        self.pack()
        ttk.Label(self, text="child label test").pack()

        self.__img = ImageTk.PhotoImage(Image.open("res/male_avatar.png").resize(
            (200, 200)))
        ttk.Label(self, image=self.__img).pack()
        ttk.Label(self, text="test label").pack()


if __name__ == '__main__':
    BaseWindow().mainloop()
Chrishdev
  • 55
  • 7
  • converting the local variable 'img' into instance variable self.__img helps it to remain in the memory for as long as the class/object remains and is not garbage collected. – Chrishdev Nov 21 '22 at 04:22