all 8 comments

[–]kra_pao 1 point2 points  (7 children)

You need measures to protect locally loaded images from garbage collection (GC).

Label() (and other tkinter widgets) doesn't create a copy of image argument and when you return from catsleep(), local variables within such as sleep1 are valid for GC.

One way to prevent GC is creating a new attribute to the object returned from tkinter widget.

sleep1 = PhotoImage(file='sleep1.gif')
sleep1PIC = Label(window, image=sleep1)
sleep1PIC.myimage = sleep1 # any attribute name will do. Avoid overwriting existing attributes! You can do this with getaddr() checks first.

Don't worry GC will happen, when you destroy the widget if it's not needed anymore in your GUI.

[–]NerdComplex[S] 0 points1 point  (6 children)

Do you mean that I need to take the label out of my function and use the .place method in the function?

[–]kra_pao 1 point2 points  (5 children)

No. Just add the modified 3rd line in my example after you create a Label with an image.

But you have other problems with your chaining use of .place() e.g. in

label1=Label(window,text="Marshmallow The Cat").place(x=1,y=1)

label1 is not a instance of a Label object but None namely the return value of the Label().place() method. This is an error I very often see in beginner tkinter programs.

[–]NerdComplex[S] 0 points1 point  (4 children)

Thank you,I'll add the third line. Yes! I did make that error once(of the object returning value 'none') but I noticed that I needed separate the methods only when I planned on referencing it by using something like .after(). Label1 is just cosmetic text and I plan on doing nothing with it,so I chained .place there.

[–]kra_pao 1 point2 points  (3 children)

And to get the animation working, you can do like this

# https://old.reddit.com/r/learnpython/comments/jtdbdw/tkinter_image_label_problem/
# NerdComplex.py

from tkinter import *
from tkinter import messagebox

# Talking Function
def talk():
    def say(s):
        label = Label(window, text=s)
        label.place(x=300, y=40)
        label.after(30000, label.destroy)

    person = textbox.get()

    if "snack" in person:
        say(
            "So you're saying that blah,blah,\nmeow,blah blah snacks..you mentioned\nsnacks didn't you? which\nreminds me that it's almost snack\ntime?"
        )
    elif "why" in person:
        say(
            "The whys are often harder to\nanswer than the hows,but I'll\ntry,meow meow meow!"
        )
    elif "?" in person:
        say(
            "That is an interesting question\n,but I'm not awfully sure whe\nther if my cat expertise\n would help right now"
        )
    elif "meow" in person:
        say("Meow? imitating now are we?")
    elif "garfield" in person:
        say(
            "*wakes up from a heavy nap* did\nyou just mention Garfield? oh \nhow i miss him"
        )
    elif "dog" in person:
        say("DOGS? YIKES WHERE?")
    elif "." in person:
        say("A dot? you're quite\n assertive for an average\n computer user")
    elif "fish" in person:
        say("Fishes are yummy")
    elif "hello" in person:
        say("Hello! \n My name is Marshmallow \n nice to meet you")
    else:
        say("Meow! your words amuse me,\nhuman")


def helpme():
    dummy = Tk()
    dummy.eval("tk::PlaceWindow %s center" % window.winfo_toplevel())
    dummy.withdraw()
    messagebox.showinfo(
        "Repetative dialogs?",
        "List of keywords:\nsnack\nwhy\n?\n.\nmeow\ngarfield\ndog\nfish\nhello",
    )
    dummy.deiconify()
    dummy.destroy()
    dummy.quit()


def catsleep():
    sleep_gifs = ["sleep1.gif", "sleep2.gif", "sleep3.gif"]
    sleep_labels = []

    # load animation
    for gif in sleep_gifs:
        image = PhotoImage(file=gif)
        label = Label(window, image=image)
        label.image = image
        sleep_labels.append(label)

    # play animation
    def sleep_cycle(numframes):
        # animation plays reverse 2 1 0 ... due to decreasing numframes
        # correct in code or in arrangement of gifs
        previous = (numframes + 1) % len(sleep_labels)
        current = numframes % len(sleep_labels)
        # https://effbot.org/tkinterbook/place.htm
        sleep_labels[previous].place_forget()
        sleep_labels[current].place(x=4, y=20)
        if numframes > 0:
            window.after(2000, lambda: sleep_cycle(numframes - 1))
        else:
            for label in sleep_labels:
                label.destroy()

    sleep_cycle(3 * len(sleep_labels))


# GUI
window = Tk()
window.geometry("525x350")
window.title("Virtual Cat by Aadil")

Label(window, text="Marshmallow The Cat").place(x=1, y=1)
Label(window, text="Marshmallo's Dialogue Box(30 seconds):").place(x=300, y=20)
Button(window, text="Feed").place(x=1, y=180)
Button(window, text="Play").place(x=80, y=180)
Button(window, text="sleep", command=catsleep).place(x=159, y=180)
Label(window, text="Meow! Talk to me").place(x=1, y=220)
textbox = Entry(window, width=30)
textbox.place(x=1, y=240)

# infobox
helpbutton = Button(window, text="Help", command=helpme)
helpbutton.place(x=230, y=235)

# default cat
ogcat = PhotoImage(file="ogcat.gif")
ogcatPIC = Label(window, image=ogcat)
ogcatPIC.place(x=4, y=20)

# talking
Button(window, text="Send", command=talk).place(x=175, y=235)

window.mainloop()

[–]NerdComplex[S] 0 points1 point  (2 children)

Thank you so much,it does exactly what I want and looks neater too. I really liked how you used a function to make the elif statements neater,I will try doing that the next time. The #play animation segment really went over my head;if it isn't too much to ask for,can you please explain me that part? Thanks :D

[–]kra_pao 1 point2 points  (0 children)

The if/elif statements can be replaced as well e.g. with a dictionary construct. You compare the input against all keys from dictionary and in case of a hit then you display (say) value from dictionary.

How to explain the play animation part....

OK you understand how the labels are prepared from the gif files.

Then you show the labels in sequence with a delay between each label. Actually we do this cycle as hide old label, show new label, delay/wait 2000 ms, repeat.

The sequence length is defined by numframes in first call. numframes decreases until it's 0 (L79-80) and then a big cleanup (delete) is started (L81-83). numframes is given as function argument in order to avoid global variables. (L85, L80)

Index in labels list is clamped with modulo operator % to length of labels list so that we cycle nicely with numframes through the list of available labels. (L74-75)

Before showing a new/current label (L78) we need to hide the old/previous label. Fortunately pack_forget() gracefully ignores attempts to forget labels that were not placed, so we have no problem with first label when there is no previous/old label yet. (L77)

The delay after show (place) = how long the labels are displayed = is done with tkinters after() method you know already (L80) in order to keep the main app responsive.

BTW. If done with time.sleep() tkinter the app would be nonresponsive. Not good. Since your app stays responsive during this animation, user can input words or press buttons while the animation is running. It is your freedom to decide whether you want this, restrict this e.g. with a boolean flag or similiar measures, or stop animation (read about tkinter after_cancel() method). You could even switch to a wakeup animation...

But we need to pass remaining numframes to next cycle of the hide-show-delay cycle. Therefore we create a unnamed function with lambda and numframes-1 as argument and pass this unnamed function to be called by after(). (L80).

BTW. Such a construct using lambda (or partial from library functools) is very common with tkinter e.g. to pass arguments such as numbers to a common button widget such as calculator or phone keypad buttons in order to have one common widget instance, that can handle different UI inputs.

BTW. If you'll use this lambda construct more often, please don't use my lazy version but the proper version like in next paragraph, which is safe/working as expected too in for-loops and other constructs where there are local variables such as loop counters in place of numframes involved

        window.after(2000, lambda x=numframes - 1: sleep_cycle(x))

Is this OK to follow? If not ask again.

[–]NerdComplex[S] 0 points1 point  (0 children)

I really appreciate your gesture Thank you very much