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. How can I make this work? Thanks!
On Sun, Nov 16, 2008 at 3:47 PM, <btkuhn@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!
_______________________________________________ Tkinter-discuss mailing list Tkinter-discuss@python.org http://mail.python.org/mailman/listinfo/tkinter-discuss
-- -- Guilherme H. Polo Goncalves
Quoting Guilherme Polo <ggpolo@gmail.com>:
On Sun, Nov 16, 2008 at 3:47 PM, <btkuhn@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!
_______________________________________________ Tkinter-discuss mailing list Tkinter-discuss@python.org http://mail.python.org/mailman/listinfo/tkinter-discuss
-- -- 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. 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) 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.
On Sun, Nov 16, 2008 at 4:24 PM, <btkuhn@email.unc.edu> wrote:
Quoting Guilherme Polo <ggpolo@gmail.com>:
On Sun, Nov 16, 2008 at 3:47 PM, <btkuhn@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
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@gmail.com>:
On Sun, Nov 16, 2008 at 4:24 PM, <btkuhn@email.unc.edu> wrote:
Quoting Guilherme Polo <ggpolo@gmail.com>:
On Sun, Nov 16, 2008 at 3:47 PM, <btkuhn@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
On Sun, Nov 16, 2008 at 9:47 PM, <btkuhn@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@gmail.com>:
On Sun, Nov 16, 2008 at 4:24 PM, <btkuhn@email.unc.edu> wrote:
Quoting Guilherme Polo <ggpolo@gmail.com>:
On Sun, Nov 16, 2008 at 3:47 PM, <btkuhn@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
participants (2)
-
btkuhn@email.unc.edu -
Guilherme Polo