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

Guilherme Polo ggpolo at gmail.com
Mon Nov 17 01:05:56 CET 2008


On Sun, Nov 16, 2008 at 9:47 PM,  <btkuhn at email.unc.edu> wrote:
> 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 *
>
> WINDOWWIDTH=500
> WINDOWHEIGHT=500
>
>
> class App:    def __init__ (self,master):
>              self.x=10
>       self.y=10
>              self.display = Frame(master, width=WINDOWWIDTH,
> height=WINDOWHEIGHT)
>              self.canvas=Canvas(self.display, bg='blue')
>              #Draw boxes, set up screen layout
>
>       self.box1=self.canvas.drawBox()
>                    self.display.pack()
>       self.canvas.pack()
>          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()
> app=App(root)
> root.mainloop()
>
> My error is "AttributeError: Canvas instance has no attribute 'drawBox'".

drawBox is a method of App, not Canvas, so you have to call it as self.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?
>

You are never passing self.canvas as self, you may think you are but
are not. The easiest form of playing with self as a Tkinter.Canvas is
subclassing this class App from Tkinter.Canvas (notice that is what I
did in the previous email).
It is hard to follow the indentation there too, so if possible put it
in a pastebin next time.

There are several things wrong, so you might consider reading the
python tutorial.

> 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
>>
>
>
>



-- 
-- Guilherme H. Polo Goncalves


More information about the Tkinter-discuss mailing list