pythoneventstkinterself

Python - Object oriented function in tkinter with event of click


my program should after click roll dice - it use six images of dice, with one, two, three, four, five and six dots. First is showed image with one dot and than after click on the image should randomly change 8 times. In the end, last random image should stay on screen, but it doesn't work and I am not able to solve it. Any idea how to fix it please? (I know that 3 classes are not necessary for this, but i will continue in it when I'll have soluted this problem) Thanks

import tkinter, random

class Main:
    def __init__(self, area, dice):
        dice.throw(area)

class Area:

    def __init__(self):
        self.canvas = tkinter.Canvas(width=1000, height=600)
        self.canvas.pack()
        self.dice1 = tkinter.PhotoImage(file='numberOne.png')
        self.canvas.create_image(680,540,image=self.dice1)
        self.canvas.bind('<Button-1>', Dice.throw)

class Dice:

    def throw(event):
        if event.x < 730 and event.x > 630 and event.y < 590 and event.y > 490:
            dice1 = tkinter.PhotoImage(file='numberOne.png')
            dice2 = tkinter.PhotoImage(file='numberTwo.png')
            dice3 = tkinter.PhotoImage(file='numberThree.png')
            dice4 = tkinter.PhotoImage(file='numberFour.png')
            dice5 = tkinter.PhotoImage(file='numberFive.png')
            dice6 = tkinter.PhotoImage(file='numberSix.png')
            for i in range(8):
                number = random.randrange(6) + 1
                if number == 1:
                    area.canvas.create_image(680,540,image=dice1)
                elif number == 2:
                    area.canvas.create_image(680,540,image=dice2)
                elif number == 3:
                    area.canvas.create_image(680,540,image=dice3)               
                elif number == 4:
                    area.canvas.create_image(680,540,image=dice4)       
                elif number == 5:
                    area.canvas.create_image(680,540,image=dice5)
                elif number == 6:
                    area.canvas.create_image(680,540,image=dice6)
                area.canvas.after(100)
                area.canvas.update()

pl = Area()
dice = Dice()
main = Main(pl, dice)

Solution

  • Problem is that Area instance (pl) need access to Dice instance (dice) and Dice instance needs access to Area instance.

    dice = Dice(root)
    area = Area(root, dice) # `Area` gets access to `Dice` instace
    dice.area = area        # `Dive` gets access to `Area` instace
    

    after that Dice can use dice.throw and Dice can use area.canvas or other elements.

    To make it simpler I assign tag to image on canvas and then I can use tag_bind to assign <Button-1> and I don't have to check event.x, event.y.

    Instead of creating new image with create_image I change image in existing element on cavas.

    create_image() gives element ID

    self.image_id = self.canvas.create_image(...)
    

    so later I can change image in this element

    self.canvas.itemconfig(self.image_id, image=image)
    

    I keep all images on list so I don't need all variables dice1, dice2, etc. and I can get image using index instead of if/elif. I can also use random.choice(self.images) to get random element from list.

    image = random.choice(self.images)
    

    Full code:

    import tkinter
    import random
    
    class Main:
    
        def __init__(self, area, dice):
            dice.throw(area)
    
    class Area:
    
        def __init__(self, master, dice):
            self.master = master
            
            self.canvas = tkinter.Canvas(master, width=1000, height=600)
            self.canvas.pack()
            
            self.image = tkinter.PhotoImage(file='numberOne.png')
            self.image_id = self.canvas.create_image(680, 540, image=self.image, tags='tag_image')
            
            self.canvas.tag_bind('tag_image', '<Button-1>', dice.throw)
    
        def change_image(self, image):
            print('changing image')
            self.canvas.itemconfig(self.image_id, image=image)
            
    class Dice:
    
        def __init__(self, master):
            self.master = master
            self.area = None # assign later
            
            self.images = [        
                tkinter.PhotoImage(file='numberOne.png')
                tkinter.PhotoImage(file='numberTwo.png')
                tkinter.PhotoImage(file='numberThree.png')
                tkinter.PhotoImage(file='numberFour.png')
                tkinter.PhotoImage(file='numberFive.png')
                tkinter.PhotoImage(file='numberSix.png')
            ]
            
        def throw(self, event):
            if self.area:
                for i in range(8):
                    image = random.choice(self.images)
                
                    self.area.change_image(image)
                
                    self.master.update()
                    self.master.after(100)
                
    # --- main ---
           
    root = tkinter.Tk()
    
    dice = Dice(root)
    area = Area(root, dice)
    dice.area = area
    
    root.mainloop()