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

btkuhn at email.unc.edu btkuhn at email.unc.edu
Mon Nov 17 00:47:08 CET 2008

Yes, this works great, thanks very much. I don't want to just copy your 
code though, I'm trying to understand what I'm doing wrong, so I tried 
modifying my code to more or less mirror your technique. I am now 
getting an unknown error message that I can't figure out, and I suspect 
that I am "thinking about things" incorrectly. I've stripped things 
down, even deleting the onDrag method for now, and still can't identify 
what I'm doing wrong. Here's the stripped down code; now I'm only 
trying to draw the box and not worrying about anything else for now:

from Tkinter import *


class App:    def __init__ (self,master):
               self.display = Frame(master, width=WINDOWWIDTH, 
               self.canvas=Canvas(self.display, bg='blue')
               #Draw boxes, set up screen layout

           def drawBox(self):
        newbox=self.create_rectangle(self.x, self.y, self.x+70, 
self.y+70, width=5, fill='red')
        self.tag_bind(newbox, '<B1-Motion>', self.onDrag)
               return newbox
        root= Tk()

My error is "AttributeError: Canvas instance has no attribute 'drawBox'".
I've tried deleting the return statement and just calling 
"self.canvas.drawBox with the same result. If I deleted the drawBox 
function and just put the statements in "init", it works correctly. I 
thought that by calling "self.canvas.drawBox()", I pass "self.canvas" 
as self, so that in effect I am calling 
"newbox=self.canvas.create_rectangle(self.x, self.y, self.x+70, 
self.y+70, width=5, fill='red')". Clearly this is inaccurate because if 
I write "newbox=self.create_rectangle(self.x, self.y, self.x+70, 
self.y+70, width=5, fill='red')" in the init, it works correctly. Where 
am I thinking about this incorrectly?

Thanks again, and sorry for all the questions - I am obviously new to 
this and it has been driving me crazy.

Quoting Guilherme Polo <ggpolo at gmail.com>:

> 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')
> app.pack()
> app.mainloop()
> --
> -- Guilherme H. Polo Goncalves

More information about the Tkinter-discuss mailing list