SpinRoleDevelop
SpinRoleDevelop

Reputation: 55

Python tkinter image can't change immediately?

My code:

from tkinter import *
import random
from PIL import ImageTk, Image
import time

class App():
    def __init__(self, root, dice_image):
        self.dice_label = Label(root, image=dice_image)
        self.dice_label.pack()
        self.button = Button(root, text="Roll", font=("Fixedsys", 25) , width=20, height=20, bg="red" , command=rolling)
        self.button.pack(pady=70)

I want to make the image change fast to make it look like rolling. But I don't know why it change nothing before the for loop end.

def rolling():
    global dices
    for i in range(1, 10 + 1):
        time.sleep(0.3)
        random.shuffle(dices)
        app.dice_label.configure(image=dices[0])


if __name__ == '__main__':
    root = Tk()
    root.geometry("800x600")

    # Var
    dice_image = ImageTk.PhotoImage(Image.open("Dices.jpg"))
    dice1 = ImageTk.PhotoImage(Image.open("Dice1.jpg"))
    dice2 = ImageTk.PhotoImage(Image.open("Dice2.jpg"))
    dice3 = ImageTk.PhotoImage(Image.open("Dice3.jpg"))
    dice4 = ImageTk.PhotoImage(Image.open("Dice4.jpg"))
    dice5 = ImageTk.PhotoImage(Image.open("Dice5.jpg"))
    dice6 = ImageTk.PhotoImage(Image.open("Dice6.jpg"))
    dices = [dice1, dice2, dice3, dice4, dice5, dice6]

    app = App(root, dice_image)

    root.mainloop()

Upvotes: 0

Views: 65

Answers (1)

Mike - SMT
Mike - SMT

Reputation: 15226

Tkinter and sleep() are not friends. The problem is that Tkinter functions inside of a mainloop and sleep pauses that loop until all the sleep time is over. This will always freeze your application.

Try using after() and a refactored function instead.

I have not tested this but I believe something like this should fix your problem or at least get rid of the problem that sleep() will cause.

I have changed your button command to send a call to the function using lambda so that the call wont go out at runtime but only when pressing the button.

from tkinter import *
import random
from PIL import ImageTk, Image


class App():
    def __init__(self, root, dice_image):
        self.dice_label = Label(root, image=dice_image)
        self.dice_label.pack()
        self.button = Button(root, text="Roll", font=("Fixedsys", 25),
                             width=20, height=20, bg="red" ,
                             command=lambda: rolling(1,11))
        self.button.pack(pady=70)

I have updated your rolling function to handly a timed loop.
root.after(300, lambda: rolling(stop, counter)) will call the function it is in only if the counter has not finished counting down and only once every 0.3 seconds. Again lambda is used here to make sure the call to rolling does not happen at runtime.

def rolling(s, e, start=False):
    global dices
    counter = 0
    stop = 0
    if start:
        counter = s
        stop = e
    if counter > stop:
        random.shuffle(dices)
        app.dice_label.configure(image=dices[0])
        counter -= 1
        root.after(300, lambda: rolling(stop, counter))

I have also updated this portion to use a loop so you don't have to repeat yourself when crating a list of images.

if __name__ == '__main__':
    root = Tk()
    root.geometry("800x600")

    dice_image = ImageTk.PhotoImage(Image.open("Dices.jpg"))
    dices = []
    for i in range(6):
        dices.append(ImageTk.PhotoImage(Image.open(f"Dice{i+1}.jpg")))

    app = App(root, dice_image)
    root.mainloop()

Upvotes: 2

Related Questions