Search code examples
pythontkintergarbage-collectionpython-imaging-library

PIL Photoimages don't display on Tkinter


I know this a common problem, but I can't seem to solve it myself. My images don't display on Tkinter. I did research, and it seems that the problem is garbage collection. I tried everything to stop it, but it seems that nothing works. Here's my full code :

import tkinter as tk
from tkinter import ttk
from lxml import etree
from os import walk
from PIL import Image, ImageTk


class Gui(tk.Tk):
    def __init__(self):
        super().__init__()
        self.frames = {}
        self.l_widgets = {}
        self.images = []  # Unused, doesn't work (nor does a dictionary)
        # self.arbre = etree.parse("characters.xml") #relative path
        self.arbre = etree.parse("C:/Users/nolan/OneDrive/Bureau/Code/wovnot/characters.xml")  # absolute path
        self.roles = {}
        for root in self.arbre.xpath('//wolvesville'):
            for pool in root.xpath('pool'):
                self.roles[str(pool.get("name"))] = {}
                for role in pool.xpath("character"):
                    self.roles[str(pool.get("name"))][role.get("name")] = (
                        role.get("surname"),
                        role.get("surname2"),
                        "r" + role.get("random"))
        self.command = {}

    def add_frame(self, name, grid_args=None, frame="main"):
        if grid_args is None:
            grid_args = {}
        if frame == "main":
            self.frames[name] = ttk.Frame(self)
        else:
            self.frames[name] = ttk.Frame(self.frames[frame])
        self.frames[name].grid(**grid_args)

    def create_entry(self, name, config_args=None, grid_args=None, frame="main"):
        if grid_args is None:
            grid_args = {}
        if config_args is None:
            config_args = {}
        if frame == "main":
            self.l_widgets[name] = (ttk.Entry(self), tk.StringVar())
        else:
            self.l_widgets[name] = (ttk.Entry(self.frames[frame]), tk.StringVar())
        self.l_widgets[name][0].config(textvariable=self.l_widgets[name][1], **config_args)
        self.l_widgets[name][0].grid(**grid_args)

    def create_notebook(self, name, config_args=None, grid_args=None, frame="main"):
        if grid_args is None:
            grid_args = {}
        if config_args is None:
            config_args = {}
        if frame == "main":
            self.l_widgets[name] = (ttk.Notebook(self, **config_args), 0)
        else:
            self.l_widgets[name] = (ttk.Notebook(self.frames[frame], **config_args), 0)
        self.l_widgets[name][0].grid(**grid_args)

    def add_image(self, name, filename, size=(None, None)):
        """Unused, doesn't work"""
        image_temp = Image.open(filename)
        if size != (None, None):
            image_temp.thumbnail(size)
        image = ImageTk.PhotoImage(image_temp)
        self.images[name] = image
        return image

    def create_a_label_with_an_image(self, name, filename, size=(None, None), config_args=None, grid_args=None,
                                     frame="main"):
        global immortals
        if grid_args is None:
            grid_args = {}
        if config_args is None:
            config_args = {}
        immortals[name] = ImagePersistent(filename, size).image
        self.l_widgets[name] = (ttk.Label(self.frames[frame]), immortals[name])
        self.l_widgets[name][0].config(**config_args)
        self.l_widgets[name][0].image = immortals[name]
        self.l_widgets[name][0].grid(**grid_args)

    def add_to_notebook(self, name, name_frame, config_args=None, grid_args=None):
        if grid_args is None:
            grid_args = {}
        if config_args is None:
            config_args = {}
        self.frames[name_frame] = ttk.Frame(self)
        self.l_widgets[name][0].add(self.frames[name_frame], **config_args)
        self.l_widgets[name][0].grid(**grid_args)


class ImagePersistent:
    def __init__(self, path, size=(None, None)):
        self.path = path
        self.temp = Image.open(path)
        if size != (None, None):
            self.temp.thumbnail(size)
        self.image = ImageTk.PhotoImage(self.temp)


# END################################################################################################################################################################"""


global immortals
immortals = {}
# also tried globals().update({"immortals":{}})
# and immortals.update({name: thing})
g = Gui()

# mypath = "icons/characters"
mypath = "C:/Users/nolan/OneDrive/Bureau/Code/wovnot/icons/characters"
filenames = next(walk(mypath), (None, None, []))[2]  # [] if no file
if not filenames:
    raise Warning("no icons found")

print(filenames)

g.create_notebook("roles", grid_args={"rowspan": 4, "column": 0})
for pool in g.roles.keys():
    g.add_to_notebook("roles", str(pool), config_args={"text": str(pool)})
    nbr_images = 0
    for icon in filenames:
        if icon[:-4] in g.roles[pool].keys():
            g.create_a_label_with_an_image(icon[:-4], mypath + "/" + icon,
                                           size=(50, 50),
                                           grid_args={"row": nbr_images // 12,
                                                      "column": nbr_images % 12},
                                           frame=str(pool))
            nbr_images += 1

g.mainloop()

Here's solutions for garbage collecting that seems to work with others didn't solve the problem :

  1. Referencing each image as a variable. Not possible in my case (I won't know how many I have at start) and while trying to do this for one image, it still wasn't shown.
  2. Keeping a reference with label.image = image. It's in my code, doesn't work. self.l_widgets[name][0].image = immortals[name],line
  3. Using a class. The ImagePersistent class didn't work either.
  4. Using a list/dictionnary/tuple. self.images doesn't work, not does putting it in self.l_widgets
  5. Using a global variable. Immortals doesn't work. I also tried globals().update({"immortals":{}}) and immortals.update({name: thing}).

So i don't know. It's still gridded, and when I didn't use a class it worked! I feel like I've tried everything. I can give links to the images (all RGBA pngs) if needed. I know they do exist, since nbr_images values goes to 57.


Solution

  • I forgot to put image as an attribute in the config part. This code works :

    def create_a_label_with_an_image(self, name, filename,size=(None,None), config_args=None, grid_args=None, frame="main"):
            global immortals
            if grid_args is None:
                grid_args = {}
            if config_args is None:
                config_args = {}
            immortals[name] = ImagePersistent(filename, size).image
            self.l_widgets[name] = (ttk.Label(self.frames[frame]), immortals[name])
            self.l_widgets[name][0].config(**config_args, image=immortals[name])
            self.l_widgets[name][0].image = immortals[name]
            self.l_widgets[name][0].grid(**grid_args)
    

    Thanks @acw1668 for telling me. I'll close the subject.