Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
338 views
in Technique[技术] by (71.8m points)

python - In tkinter, is this a good way of avoiding unwanted garbage collection?

In Tkinter, we sometimes have to create a reference to a converted image (for example), so that it will not be destroyed when it goes out of scope.

A common way is to add a variable to its widget instance. An example of this is:

    bard = Image.open("bardejov.jpg")
    bardejov = ImageTk.PhotoImage(bard)
    label1 = Label(self, image=bardejov)
    label1.image = bardejov #<<<<<<<<<<<<<<<<<<<<<
    label1.place(x=20, y=20)

This is part of an example published by Jan Bodnar of Zetcode, with my flagging of the example. bardejov is a local variable in a function, and if you comment out the marked line you don't get the image, because it is destroyed when the function returns, and the label just sees 'none'.

I'm new to Tkinter, and this worried me rather, adding new properties to system code, and someone suggested this:

class S():
    # To make an object 'accessible', and thus save 
    # it from garbage collection.
    fred = []
    @classmethod
    def save(cls, x):
        cls.fred.append(x)

This certainly worked in Jan's example:

bard = Image.open("xxxx.jpg")
bardejov = ImageTk.PhotoImage(bard)
label1 = Label(self, image=bardejov)
#label1.image = bardejov
S.save(bardejov)

But is it OK? Any undesirable side effects?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Question: good way of avoiding unwanted garbage collection?

It's not the question, good or not, you have to do it.
Instead of doing it over and over again you can define your own PhotoImageLabel.

For example:

import tkinter as tk
from PIL import Image, ImageTk


class PhotoImageLabel(tk.Label):
    def __init__(self, parent, **kwargs):
        image = Image.open(kwargs['image'])
        self._image = ImageTk.PhotoImage(image)

        kwargs['image'] = self._image
        super().__init__(parent, **kwargs)

Usage:

class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.photo_label = PhotoImageLabel(self, image="xxxx.jpg")
        self.photo_label.grid()


if __name__ == '__main__':
    App().mainloop()

Comment: One day I guess the widget itself will fix the problem

That is unlikely, because ImageTk.PhotoImage is not part of tkinter. You can extend PhotoImage to behave it like a tkinter object, by binding the image object to the widget:

For example:

class PhotoImage(ImageTk.PhotoImage):
    def __init__(self, parent, **kwargs):
        image = kwargs['image']

        # PIL.Image => .PhotoImage
        super().__init__(image)

        # Update <widget>.children with [self.__photo.name] = self
        self._ref = parent.children
        self._ref[self.__photo.name] = self

    def destroy(self):
        # This gets called on `.destroy()` the parent
        # Delete the reference in parent.children
        del self._ref[self.__photo.name]

Usage:

class PhotoImageLabel(tk.Label):
    def __init__(self, parent, **kwargs):
        image = Image.open(kwargs['image'])

        # Get a PhotoImage object which is bound to 'self'
        kwargs['image'] = PhotoImage(self, image=image)

        super().__init__(parent, **kwargs)

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...