Search code examples
pythoneventstkintermove

Moving more objects in Python tkinter


I'm making simple game - there're 8 ovals, they should be clickable and moveable. After click on an oval, the oval is following cursor. The target is to get the oval into rectangle, if you release mouse button in rectangle, the oval disappears. If you release mouse button outside rectangle, the oval should appear on it's initial position. I made this program and it works, but only for one oval. I need it works for all ovals. There's my code, any idea what to change, please?

import tkinter, random

class Desktop:

    array = [(50,50,70,70),(100,50,120,70),(150,50,170,70),(150,100,170,120),
            (150,150,170,170),(100,150,120,170),(50,150,70,170),(50,100,70,120)]

    def __init__(self):
        self.canvas = tkinter.Canvas(width=400,height=400)
        self.canvas.pack()
        self.canvas.create_rectangle(100,250,300,350)
        for i in range(len(self.array)):
            self.__dict__[f'oval{i}'] = self.canvas.create_oval(self.array[i], fill='brown',tags='id')
        self.canvas.tag_bind('id','<B1-Motion>',self.move)
        self.canvas.tag_bind('id','<ButtonRelease-1>',self.release)

    def move(self, event):
        self.canvas.coords(self.oval0,event.x-10,event.y-10,event.x+10,event.y+10)

    def release(self, event):
        if event.x>100 and event.x<300 and event.y>250 and event.y<350:
            self.canvas.delete(self.oval0)
        else:
            self.canvas.coords(self.oval0,self.array[0])


d = Desktop()

Solution

  • I bind three methods

    • <ButtonPress-1> to get ID of clicked item and assign to self.selected
    • <B1-Motion> to move item using self.selected
    • <ButtonRelease-1> to release or delete moved item using self.selected

    Code

    import tkinter as tk
    import random
    
    class Desktop:
    
        array = [(50,50,70,70),(100,50,120,70),(150,50,170,70),(150,100,170,120),
                (150,150,170,170),(100,150,120,170),(50,150,70,170),(50,100,70,120)]
    
        def __init__(self, master):
            self.canvas = tk.Canvas(master, width=400, height=400)
            self.canvas.pack()
    
            self.canvas.create_rectangle(100, 250, 300, 350)
    
            # to keep all IDs and its start position
            self.ovals = {}
    
            for item in self.array:
                # create oval and get its ID
                item_id = self.canvas.create_oval(item, fill='brown', tags='id')
                # remember ID and its start position
                self.ovals[item_id] = item
    
            self.canvas.tag_bind('id', '<ButtonPress-1>', self.start_move)
            self.canvas.tag_bind('id', '<B1-Motion>', self.move)
            self.canvas.tag_bind('id', '<ButtonRelease-1>', self.stop_move)
    
            # to remember selected item
            self.selected = None
    
        def start_move(self, event):
            # find all clicked items
            self.selected = self.canvas.find_overlapping(event.x, event.y, event.x, event.y)
            # get first selected item
            self.selected = self.selected[0]
    
        def move(self, event):
            # move selected item
            self.canvas.coords(self.selected, event.x-10, event.y-10, event.x+10,event.y+10)
    
        def stop_move(self, event):
            # delete or release selected item
            if 100 < event.x < 300 and 250 < event.y < 350:
                self.canvas.delete(self.selected)
                del self.ovals[self.selected]
            else:
                self.canvas.coords(self.selected, self.ovals[self.selected])
            # clear it so you can use it to check if you are draging item
            self.selected = None
    
    root = tk.Tk()
    d = Desktop(root)
    root.mainloop()
    

    EDIT: using event.widget.find_withtag("current")[0] I can get first selected item, and I can skip <ButtonPress-1>.

    import tkinter as tk
    import random
    
    class Desktop:
    
        array = [(50,50,70,70),(100,50,120,70),(150,50,170,70),(150,100,170,120),
                (150,150,170,170),(100,150,120,170),(50,150,70,170),(50,100,70,120)]
    
        def __init__(self, master):
            self.canvas = tk.Canvas(master, width=400, height=400)
            self.canvas.pack()
    
            self.canvas.create_rectangle(100, 250, 300, 350)
    
            # to keep all IDs and its start position
            self.ovals = {}
    
            for item in self.array:
                # create oval and get its ID
                item_id = self.canvas.create_oval(item, fill='brown', tags='id')
                # remember ID and its start position
                self.ovals[item_id] = item
    
            self.canvas.tag_bind('id', '<B1-Motion>', self.move)
            self.canvas.tag_bind('id', '<ButtonRelease-1>', self.stop_move)
    
        def move(self, event):
            # get selected item
            selected = event.widget.find_withtag("current")[0]
    
            # move selected item
            self.canvas.coords(selected, event.x-10, event.y-10, event.x+10,event.y+10)
    
        def stop_move(self, event):
            # get selected item
            selected = event.widget.find_withtag("current")[0]
    
            # delete or release selected item
            if 100 < event.x < 300 and 250 < event.y < 350:
                self.canvas.delete(selected)
                del self.ovals[selected]
            else:
                self.canvas.coords(selected, self.ovals[selected])
    
    root = tk.Tk()
    d = Desktop(root)
    root.mainloop()
    

    EDIT: added del self.ovals[selected]