[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