[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 *
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'".
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