[Tkinter-discuss] How to bind to a drawn object?

Guilherme Polo ggpolo at gmail.com
Sun Nov 16 20:00:14 CET 2008

On Sun, Nov 16, 2008 at 4:24 PM,  <btkuhn at email.unc.edu> wrote:
> Quoting Guilherme Polo <ggpolo at gmail.com>:
>> On Sun, Nov 16, 2008 at 3:47 PM,  <btkuhn at email.unc.edu> wrote:
>>> Hello everyone,
>>> I am trying to create bindable shapes with Tkinter that are dragged when
>>> the
>>> user drags with the mouse. Here is an example (some code edited out for
>>> simplicity). This is all within an "App" class:
>>> self.canvas=Canvas(self.display, bg='blue')
>>> self.canvas.bind('<B1-Motion>', self.onDrag)
>>> self.x=10
>>> self.y=10
>>> #Draw ball, set up screen layout
>>>           self.box1=self.drawBox(self.canvas)
>>>      self.canvas.pack()
>>>         def drawBox(self,master):
>>>      newbox=master.create_rectangle(self.x, self.y, self.x+70, self.y+70,
>>> width=5, fill='red')
>>>             return newbox
>>>     def onDrag(self,event):
>>>      self.canvas.move(self.box1,event.x-self.x,event.y-self.y)
>>>      self.x=event.x
>>>      self.y=event.y
>>> This works as written, but it is not good form because it can only be
>>> used
>>> with box1. <B1-Motion> is bound to the canvas rather that the box, and
>>> "self.box1" is explicitly stated in the onDrag method. Therefore, if I
>>> wanted to create 10 boxes (or even 2) I would have to write a new onDrag
>>> method for each one. Also, if I want the box to only drag when the user
>>> clicks INSIDE the box, I would have to create a new "dimensions" list for
>>> each new box, and then use an if statement to see if the mouse is inside
>>> the
>>> box, etc. I actually tried doing this and it makes the program run
>>> extremely
>>> slow with only one box, so I don't think this is a good solution. It
>>> seems
>>> logical to bind <B1-Motion> to box1 rather than the canvas, and that way
>>> the
>>> box will react only if the mouse is inside the box, and I could create
>>> multiple boxes, each with its own binding to <B1-Motion>. When I try to
>>> do
>>> this, though, using something like this:
>>> newbox=master.create_rectangle(self.x, self.y, self.x+70, self.y+70,
>>> width=5, fill='red')
>>> newbox.bind('<B1-Motion>', self.onDrag)
>>> it gives me an error saying that I can't bind this method to this
>>> instance.
>> Didn't you mean an AttributeError ? The id returned my
>> create_rectangle surely doesn't have a bind method.
>>> How can I make this work?
>> That is why Canvas has a method called tag_bind, to bind an item in the
>> canvas:
>> master.tag_bind(newbox, '<B1-Motion>', self.on_drag)
>>> Thanks!
>> --
>> -- Guilherme H. Polo Goncalves
> Thanks. Yes, you're right, the message is "AttributeError: App instance has
> no attribute 'on_drag'" However, when I make this change I get no response
> at all when the program is run.

I suspect there are other things wrong in your app then.

> My drawBox function now looks like this:
>   def drawBox(self,master):
>       newbox=master.create_rectangle(self.x, self.y, self.x+70, self.y+70,
> width=5, fill='red')
>       master.tag_bind(newbox, '<B1-Motion>', self.onDrag)

Don't you want a ButtonPress-1 there too ?

> and my onDrag function looks like this:
>   def onDrag(self,event):
>       self.canvas.move(self,event.x-self.x,event.y-self.y)
>       self.x=event.x
>       self.y=event.y
> This looks like it should work: it passed newbox as self and the coordinates
> as event to the onDrag function, but I get no response at all.

Uhm, no, self is the class instance so it is surely not the newbox item.

Well, I've done a sample here based on what you are after, check if
this works for you:

import Tkinter

class Boxes(Tkinter.Canvas):
    def __init__(self, master=None, **kw):
        Tkinter.Canvas.__init__(self, master, **kw)

    def create_box(self, x, y, size, **kw):
        box = self.create_rectangle(x, y, x + size, y + size, **kw)
        self.tag_bind(box, '<ButtonPress-1>', self.on_click)
        self.tag_bind(box, '<B1-Motion>', self.on_drag)

    def on_click(self, event):
        self.curr_x = event.x
        self.curr_y = event.y

    def on_drag(self, event):
        x, y, _, _ = self.coords('current')
        self.move('current', (event.x - self.curr_x), (event.y - self.curr_y))
        self.curr_x = event.x
        self.curr_y = event.y

app = Boxes(bg='blue')
app.create_box(10, 10, 70, width=5, fill='red')
app.create_box(20, 20, 70, width=5, fill='yellow')

-- Guilherme H. Polo Goncalves

More information about the Tkinter-discuss mailing list