Search code examples
pythonobjecttkintermove

Tkinter : Moving more than one object at once


I am still fairly new to tkinter and I am trying to create a network simulator. I have made a simpler version of my program without all of the extra network stuff. I have been stuck on a problem for quite a while now. I can't work out how to make it so when I try and move one of the circles, it moves the text with it. I had an idea to delete the text when I click down so I can then move the object and recreate the text when I drop the object but I am not sure how to do this. Any other ideas are certainly welcome.

This is my library:

from Tkinter import *
import time

#Window class for making different windows

class Window:

#Constructor
def __init__(self, window, colour="black", width=600, height=400):
    #Set variables
    self.width = width
    self.height = height
    self.colour = colour

    #This dictionary is used to keep track of an item being dragged
    self._drag_data = {"x": 0, "y": 0, "item": None}

    #Create canvas
    self.canvas = Canvas(window, bg=self.colour, height=self.height, width=self.width)
    self.canvas.pack()

    #Add bindings for clicking, dragging and releasing over any object with the "circledrag" tag
    self.canvas.tag_bind("circledrag", "<ButtonPress-1>", self.OnCircleButtonPress)
    self.canvas.tag_bind("circledrag", "<ButtonRelease-1>", self.OnCircleButtonRelease)
    self.canvas.tag_bind("circledrag", "<B1-Motion>", self.OnCircleMotion)

#This is used to draw particle objects on the canvas, notice the tag that has been added as an attribute
def _create_circle(self, xcoord, ycoord, color):
    self.canvas.create_oval(xcoord-25, ycoord-25, xcoord+25, ycoord+25, outline=color, fill=color, tags = "circledrag")

#This is used to draw text on top of the object on the canvas
def _create_text(self, xcoord, ycoord, text):
    self.canvas.create_text(xcoord, ycoord, text = text, tags = ("circledrag", "text"))

#This uses the find_closest method to get store the x and y positions of the nearest item into the dictionary
def OnCircleButtonPress(self, event):
    #print self.canvas.find_withtag("Current")
    self.canvas.delete("text")

    '''Begin drag of an object'''
    # record the item and its location
    self._drag_data["item"] = self.canvas.find_closest(event.x, event.y)[0]
    self._drag_data["x"] = event.x
    self._drag_data["y"] = event.y

#This clears the dictionary once the mouse button has been released
def OnCircleButtonRelease(self, event):
    '''End drag of an object'''
    # reset the drag information
    self._drag_data["item"] = None
    self._drag_data["x"] = 0
    self._drag_data["y"] = 0

#This moves the item as it is being dragged around the screen
def OnCircleMotion(self, event):
    '''Handle dragging of an object'''
    # compute how much this object has moved
    delta_x = event.x - self._drag_data["x"]
    delta_y = event.y - self._drag_data["y"]
    # move the object the appropriate amount
    self.canvas.move(self._drag_data["item"], delta_x, delta_y)
    # record the new position
    self._drag_data["x"] = event.x
    self._drag_data["y"] = event.y

class DragCircle:
#Constructor
def __init__(self, window, width=100, height=100, colour="red", text = "test"):
    self.window = window
    self.circle = self.window._create_circle(width, height, colour)
    self.circle_text = self.window._create_text(width, height, text)

and this is the main program:

from Tkinter import *
import my_module2

#Make a window from my own class
window = Tk()
window.title("Drag & Drop")

#Create an instance of the window class
main_window = my_module2.Window(window)

#Create a circle object from the DragCircle class
circle = my_module2.DragCircle(main_window)
circle2 = my_module2.DragCircle(main_window, 200, 200, "green", "hello")

#Start the animation loop
window.mainloop()

I would like to know how to move two objects at once.


Solution

  • Each time you create a circle/text pair, create a unique tag and associate the tag with both the circle and the text. You can then use this unique tag to move both objects at once.

    For example, modify your DragCircle constructor to look like this:

    class DragCircle:
        #Constructor
        def __init__(self, window, width=100, height=100, colour="red", text = "test"):
            self.window = window
            tag = "circle-%d" % id(self)
            self.circle = self.window._create_circle(width, height, colour, tag)
            self.circle_text = self.window._create_text(width, height, text, tag)
    

    Next, modify the _create_circle and _create_text functions to accept the tag:

    def _create_circle(self, xcoord, ycoord, color, tag):
        id_=self.canvas.create_oval(xcoord-25, ycoord-25, xcoord+25, ycoord+25, outline=color, fill=color, tags = ("circledrag", tag))
    
    #This is used to draw text on top of the object on the canvas
    def _create_text(self, xcoord, ycoord, text, tag):
        self.canvas.create_text(xcoord, ycoord, text = text, tags = ("circledrag", "text", tag))
    

    Finally, modify your OnCircleButtonPress to get the tag and use it for moving the objects around:

    def OnCircleButtonPress(self, event):
        ...
        item = self.canvas.find_closest(event.x, event.y)[0]
        tags = self.canvas.gettags(item)
        for tag in tags:
            if tag.startswith("circle-"):
                break
        self._drag_data["item"] = tag
        ...