0

So basically i have a function called click_roll where if you click the roll button it will roll dice and show the result as an image of a dice side, for some reason the program will only work if the function had and error at the end of it! how does this make any sense? (the error_variable at the end of the function doesnt exist so adding 1 to it will cause an error)

from tkinter import *
import random
from PIL import ImageTk,Image
root = Tk()
root.geometry('300x400')
def rolldice():
    number = random.randint(1,6)
    return number
def click_roll():
    number = rolldice()
    if number == 2:
        image1 = ImageTk.PhotoImage(Image.open("dice2.jpg"))
        image1_label = Label(image=image1)
        image1_label.place(relx=0.5, rely=0.3, anchor=CENTER)
    elif number == 3:
        image1 = ImageTk.PhotoImage(Image.open("dice3.jpg"))
        image1_label = Label(image=image1)
        image1_label.place(relx=0.5, rely=0.3, anchor=CENTER)
    elif number == 4:
        image1 = ImageTk.PhotoImage(Image.open("dice4.jpg"))
        image1_label = Label(image=image1)
        image1_label.place(relx=0.5, rely=0.3, anchor=CENTER)
    elif number == 5:
        image1 = ImageTk.PhotoImage(Image.open("dice5.jpg"))
        image1_label = Label(image=image1)
        image1_label.place(relx=0.5, rely=0.3, anchor=CENTER)
    elif number == 6:
        image1 = ImageTk.PhotoImage(Image.open("dice6.jpg"))
        image1_label = Label(image=image1)
        image1_label.place(relx=0.5, rely=0.3, anchor=CENTER)
    elif number == 1:
        image1 = ImageTk.PhotoImage(Image.open("dice1.jpg"))
        image1_label = Label(image=image1)
        image1_label.place(relx=0.5, rely=0.3, anchor=CENTER)
    
    error_variable +=1


image1 = ImageTk.PhotoImage(Image.open("dice1.jpg"))
image1_label = Label(image=image1)

roll_button = Button(root,text='Roll',height=5,width=12,command=click_roll)

roll_button.grid(row=0,column=1)
image1_label.place(relx=0.5, rely=0.3, anchor=CENTER)
roll_button.place(relx=0.5, rely=0.5, anchor=CENTER)

root.mainloop()
Wombatz
  • 4,958
  • 1
  • 26
  • 35
  • The question is not clear, the program is not reproducible and has errors in the code. – Сергей Кох Jul 20 '22 at 08:25
  • i ran it and it worked tho, stackoverflow couldve bugged it somehow – NatsumeFlowers Jul 20 '22 at 08:30
  • all the conditionals run the same exact code except for the image number. why not just doing `image1 = ImageTk.PhotoImage(Image.open(f"dice{number}.jpg"))` and get rid of all the conditionals? It would simplify the code enormously –  Jul 20 '22 at 08:56
  • Next time, please make sure that your code does not contain indentation errors. People are more likely to help if the code can be copied **as is** into a text editor to reproduce the error. – Wombatz Jul 20 '22 at 09:03
  • Sorry copying the code to stackoverflow was pretty complicated – NatsumeFlowers Jul 21 '22 at 15:33

2 Answers2

2

The class ImageTk.PhotoImage has an implementation for __del__ that deletes the image from tkinter (not sure what exactly happens here, as i don't use tkinter).

Your click_roll creates an image and drops all references to it (the label for some reason does not keep a reference to the image). Eventually the __del__ method on the image is called and it is removed from the main window.

When you raise an exception inside the click_roll function, the traceback will keep the frame alive and thus the image is not removed from the main window.

To prevent that from happening, you should restructure your code a bit so that the images are stored in a "safe" location.

# store the images in a variable that "survives" the click_roll handler
die_images = {
    1: ImageTk.PhotoImage('dice1.jpg'),
    ...
    6: ImageTk.PhotoImage('dice6.jpg')
}


def click_roll():
    number = rolldice()
    image = die_images[number]  # pick the correct image
    label = Label(image=image)
    ...

If you are familiar with dict-comprehensions you could also simplify the die_images creation:

die_images = {number: ImageTk.PhotoImage(f'dice{number}.jpg') for number in range(1, 7)}

Final thoughts: this seems extremely weird, maybe a tkinter expert can comment if this is expected behaviour. In my opinion the the label should keep a reference to the image. But maybe the interaction between tkinter and Pillow is just suboptimal.

Wombatz
  • 4,958
  • 1
  • 26
  • 35
  • no `tkinter` expert, but yeah, this is about expected behavior, garbage collection deletes those images. why, idk (the obvious reason is that it collects the unused garbage which the image is and apparently setting it as the label's image doesn't create any references), maybe to save memory or sth, but it's just simply expected, so a reference must be kept to avoid it from happening – Matiiss Jul 20 '22 at 10:10
0

The picture disappears, because after the function terminates, the new label with the image is eaten by the garbage collector. Instead of creating a new label, you just need to replace the image on it and create an additional link to the image, and taking into account Sembei Norimaki's comment, the click_roll() function code:

def click_roll():

    number = rolldice()
    image1 = ImageTk.PhotoImage(Image.open(f'heart{number}.png'))
    image1_label.configure(image=image1)
    image1_label.image = image1
Сергей Кох
  • 1,417
  • 12
  • 6
  • 13