On the output screen, there will be a rectangle enclosed in a rectangle if I drag the outer rectangle
inside rectangle should also be dragged but only the outer one is getting dragged. How should the entire contents in the outer rectangle get dragged?. In any shape, if the shape is dragged contents in it also should be dragged but contents are not getting dragged only outer shape is getting dragged.
Here is my code.
import tkinter as tk # python 3
# import Tkinter as tk # python 2
class Example(tk.Frame):
"""Illustrate how to drag items on a Tkinter canvas"""
def __init__(self, parent):
tk.Frame.__init__(self, parent)
# create a canvas
self.canvas = tk.Canvas(width=400, height=400, background="bisque")
self.canvas.pack(fill="both", expand=True)
# this data is used to keep track of an
# item being dragged
self._drag_data = {"x": 0, "y": 0, "item": None}
# create a couple of movable objects
#self.create_token(50, 100, "white")
self.create_token(200, 100, "black")
self.create_token1(200,100,"white")
# add bindings for clicking, dragging and releasing over
# any object with the "token" tag
self.canvas.tag_bind("token", "<ButtonPress-1>", self.drag_start)
self.canvas.tag_bind("token", "<ButtonRelease-1>", self.drag_stop)
self.canvas.tag_bind("token", "<B1-Motion>", self.drag)
def create_token(self, x, y, color):
"""Create a token at the given coordinate in the given color"""
self.canvas.create_rectangle(
x - 25,
y - 25,
x + 25,
y + 25,
outline=color,
fill=color,
tags=("token",),
)
def create_token1(self,x,y,color):
self.canvas.create_rectangle(
x - 25,
y - 10,
x + 25,
y + 10,
outline=color,
fill=color,
tags=("token",),
)
def drag_start(self, event):
"""Begining drag of an object"""
# record the item and its location
self._drag_data["item"] = self.canvas.find_closest(event.x, event.y)[0]
self._drag_data["x"] = event.x
self._drag_data["y"] = event.y
def drag_stop(self, event):
"""End drag of an object"""
# reset the drag information
self._drag_data["item"] = None
self._drag_data["x"] = 0
self._drag_data["y"] = 0
def drag(self, event):
"""Handle dragging of an object"""
# compute how much the mouse has moved
delta_x = event.x - self._drag_data["x"]
delta_y = event.y - self._drag_data["y"]
# move the object the appropriate amount
self.canvas.move(self._drag_data["item"], delta_x, delta_y)
# record the new position
self._drag_data["x"] = event.x
self._drag_data["y"] = event.y
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
In drag_start() I use outer rectangle to get its region and add tag "drag" to all elements which are fully inside this region
rect = self.canvas.bbox(self._drag_data["item"])
self.canvas.addtag_enclosed("drag", *rect)
In dra() I move all elements with tag "drag"
self.canvas.move("drag", delta_x, delta_y)
In drag_stop() I remove tag "drag" from all elements which have tag "drag"
self.canvas.dtag("drag", "drag")
This way outer rectangle can move also inner rectangle. But if you move inner rectangle then inner rectangle doesn't move. If you want outer rectangle when you move innter rectangle then maybe you should use tag "token"
self.canvas.move("token", delta_x, delta_y)
import tkinter as tk # python 3
# import Tkinter as tk # python 2
class Example(tk.Frame):
"""Illustrate how to drag items on a Tkinter canvas"""
def __init__(self, parent):
tk.Frame.__init__(self, parent)
# create a canvas
self.canvas = tk.Canvas(width=400, height=400, background="bisque")
self.canvas.pack(fill="both", expand=True)
# this data is used to keep track of an
# item being dragged
self._drag_data = {"x": 0, "y": 0, "item": None}
# create a couple of movable objects
#self.create_token(50, 100, "white")
self.create_token(200, 100, "black")
self.create_token1(200,100,"white")
# add bindings for clicking, dragging and releasing over
# any object with the "token" tag
self.canvas.tag_bind("token", "<ButtonPress-1>", self.drag_start)
self.canvas.tag_bind("token", "<ButtonRelease-1>", self.drag_stop)
self.canvas.tag_bind("token", "<B1-Motion>", self.drag)
def create_token(self, x, y, color):
"""Create a token at the given coordinate in the given color"""
self.canvas.create_rectangle(
x - 25,
y - 25,
x + 25,
y + 25,
outline=color,
fill=color,
tags=("token",),
)
def create_token1(self,x,y,color):
self.canvas.create_rectangle(
x - 25,
y - 10,
x + 25,
y + 10,
outline=color,
fill=color,
tags=("token",),
)
def drag_start(self, event):
"""Begining drag of an object"""
# record the item and its location
self._drag_data["item"] = self.canvas.find_closest(event.x, event.y)[0]
rect = self.canvas.bbox(self._drag_data["item"])
self.canvas.addtag_enclosed("drag", *rect)
print(rect)
self._drag_data["x"] = event.x
self._drag_data["y"] = event.y
def drag_stop(self, event):
"""End drag of an object"""
# reset the drag information
self._drag_data["item"] = None
self._drag_data["x"] = 0
self._drag_data["y"] = 0
self.canvas.dtag("drag", "drag")
def drag(self, event):
"""Handle dragging of an object"""
# compute how much the mouse has moved
delta_x = event.x - self._drag_data["x"]
delta_y = event.y - self._drag_data["y"]
# move the object the appropriate amount
#self.canvas.move(self._drag_data["item"], delta_x, delta_y)
self.canvas.move("drag", delta_x, delta_y)
# record the new position
self._drag_data["x"] = event.x
self._drag_data["y"] = event.y
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
These minor changes to the previous example prevent irrevocably dragging objects off of the canvas (mostly see the drag function). And thanks for the previous example.
import tkinter as tk # python 3
# import Tkinter as tk # python 2
class Example(tk.Frame):
"""Illustrate how to drag items on a Tkinter canvas"""
def __init__(self, parent):
tk.Frame.__init__(self, parent)
# create a canvas
self.canvas = tk.Canvas(self,width=400, height=400, background="bisque")
self.canvas.pack(fill="both", expand=True)
# this data is used to keep track of an
# item being dragged
self._drag_data = {"x": 0, "y": 0, "item": None}
# create a couple of movable objects
#self.create_token(50, 100, "white")
self.create_token(200, 100, "black")
self.create_token1(200,100,"white")
# add bindings for clicking, dragging and releasing over
# any object with the "token" tag
self.canvas.tag_bind("token", "<ButtonPress-1>", self.drag_start)
self.canvas.tag_bind("token", "<ButtonRelease-1>", self.drag_stop)
self.canvas.tag_bind("token", "<B1-Motion>", self.drag)
def create_token(self, x, y, color):
"""Create a token at the given coordinate in the given color"""
self.canvas.create_rectangle(
x - 25,
y - 25,
x + 25,
y + 25,
outline=color,
fill=color,
tags=("token",),
)
def create_token1(self,x,y,color):
self.canvas.create_rectangle(
x - 20,
y - 10,
x + 20,
y + 5,
outline=color,
fill=color,
tags=("token",),
)
def drag_start(self, event):
"""Begining drag of an object"""
# record the item and its location
self._drag_data["item"] = self.canvas.find_closest(event.x, event.y)[0]
rect = self.canvas.bbox(self._drag_data["item"])
self.canvas.addtag_enclosed("drag", *rect)
print(rect)
self._drag_data["x"] = event.x
self._drag_data["y"] = event.y
def drag_stop(self, event):
"""End drag of an object"""
# reset the drag information
self._drag_data["item"] = None
self._drag_data["x"] = 0
self._drag_data["y"] = 0
self.canvas.dtag("drag", "drag")
def drag(self, event):
"""Handle dragging of an object"""
# compute how much the mouse has moved
delta_x = event.x - self._drag_data["x"]
delta_y = event.y - self._drag_data["y"]
w=self.winfo_width()
h=self.winfo_height()
rect = self.canvas.bbox(self._drag_data["item"])
if 0:
##don't allow any part of items to move off the canvas
if rect[3]+delta_y > h: delta_y=0 #stop down
if rect[1]+delta_y < 0: delta_y=0 #stop up
if rect[2]+delta_x > w: delta_x=0 #stop right
if rect[0]+delta_x < 0: delta_x=0 #stop down
else:
##don't allow the last 10 pixels to move off the canvas
pixels=10
if rect[1]+delta_y+pixels > h: delta_y=0 #stop down
if rect[3]+delta_y-pixels < 0: delta_y=0 #stop up
if rect[0]+delta_x+pixels > w: delta_x=0 #stop right
if rect[2]+delta_x-pixels < 0: delta_x=0 #stop down
# move the object the appropriate amount
#self.canvas.move(self._drag_data["item"], delta_x, delta_y)
self.canvas.move("drag", delta_x, delta_y)
# record the new position
self._drag_data["x"] = event.x
self._drag_data["y"] = event.y
if __name__ == "__main__":
root = tk.Tk()
root.geometry("800x500")
#Example(root).pack(fill="both", expand=True)
Example(root).place(relx=0.1,rely=0.1,relwidth=0.8,relheight=0.8)
root.mainloop()
Related
I want to draw some blocks and connect them through arrow.
I did it, but if I move the coordinates of blocks I also need to change arrow coordinates.
Is there any method to bind them, like if I move the block position connecting arrow will also adjust automatically?
Can you suggest any other method in python to which will work?
from tkinter import *
top = Tk()
top.geometry("800x600")
#creating a simple canvas
a = Canvas(top,bg = "White",height = "515", width = "1000")
a.create_rectangle(430, 255, 550, 275,fill="White")
a.create_text(490,265,text="School",fill="black",font=('Helvetica 8 bold'))
a.create_rectangle(430, 205, 480, 225,fill="White")
a.create_text(455,215,text="Boys",fill="black",font=('Helvetica 8 bold'))
a.create_rectangle(480, 160, 540, 180,fill="White")
a.create_text(510,170,text="Girls",fill="black",font=('Helvetica 8 bold'))
#School to Boys
a.create_line(450, 225, 450, 255, arrow= BOTH)
#School to COM
a.create_line(510, 180, 510, 255, arrow= BOTH)
a.pack(expand=True)
top.mainloop()
No, there isn't any method given by tkinter to link blocks. But, you can create them yourself. First, create a rectangle and text and assign them the same tag (note: each block must have a unique tag).
Now, if you want to move the blocks using the mouse, you will have to use the canvas.find_withtag("current") to get the currently selected block and canvas.move(tagorid, x, y) to move the block. Once it is moved use widget.generate_event() to generate a custom virtual event.
You will now have to use canvas.tag_bind(unqiue_tagn, "<<customevent>>", update_pos) to call the update_pos function when the event is generated, the update_pos should update the connection position.
Here is a sample code.
import tkinter as tk
class NodeScene(tk.Canvas):
def __init__(self, *args, **kwargs):
super(NodeScene, self).__init__(*args, **kwargs)
self.bind("<Button-1>", self.click_pos)
self.bind("<B1-Motion>", self.selected_item)
self.prev_x, self.prev_y = 0, 0
def click_pos(self, event):
self.prev_x, self.prev_y = event.x, event.y
def selected_item(self, event):
_current = self.find_withtag("current")
assoc_tags = self.itemcget(_current, "tags").split(" ")[0]
if _current and "node" in assoc_tags:
new_pos_x, new_pos_y = event.x - self.prev_x, event.y - self.prev_y
self.move(assoc_tags, new_pos_x, new_pos_y)
self.event_generate("<<moved>>", when="tail")
self.prev_x, self.prev_y = event.x, event.y
class Node:
def __init__(self, canvas: tk.Canvas, text: str, pos=(50, 50)):
self.tag_id = f"node{id(self)}"
self.canvas = canvas
self.c_rect = self.canvas.create_rectangle(0, 0, 0, 0, fill="white", tags=(self.tag_id))
c_text = self.canvas.create_text(*pos, text=text, fill="black", font=("Helvetica 8 bold"), tags=(self.tag_id))
padding = 10
text_rect = list(self.canvas.bbox(c_text))
text_rect[0] -= padding
text_rect[1] -= padding
text_rect[2] += padding
text_rect[3] += padding
self.canvas.coords(self.c_rect, *text_rect)
def bbox(self):
return self.canvas.bbox(self.c_rect)
def top(self):
bbox = self.bbox()
return (bbox[2] + bbox[0]) / 2, bbox[1]
def bottom(self):
bbox = self.bbox()
return (bbox[2] + bbox[0]) / 2, bbox[3]
class Connection:
def __init__(self, canvas, node1: Node, node2: Node, pos_x): # pos_x is the offset from center
self.node1 = node1
self.node2 = node2
self.pos_x_offset = pos_x
self.canvas = canvas
self.connection_id = self.canvas.create_line(0, 0, 0, 0, arrow= tk.BOTH)
self.canvas.tag_bind(self.node1.tag_id, "<<moved>>", self.update_conn_pos, add="+")
self.canvas.tag_bind(self.node2.tag_id, "<<moved>>", self.update_conn_pos, add="+")
self.update_conn_pos()
def update_conn_pos(self, event=None):
""" updates the connection position """
node1_btm = self.node1.bottom()
node2_top = list(self.node2.top())
node2_top[0] += self.pos_x_offset
self.canvas.coords(self.connection_id, *node1_btm, *node2_top)
root = tk.Tk()
canvas = NodeScene(root)
canvas.pack(expand=True, fill="both")
node = Node(canvas, "School", pos=(100, 150))
node1 = Node(canvas, "boys", pos=(80, 80))
node2 = Node(canvas, "girls", pos=(120, 30))
connection = Connection(canvas, node1, node, -20)
connection2 = Connection(canvas, node2, node, 20)
root.mainloop()
I am using Tkinter to import images with Openslide. I would like to integrate a manual annotation module into my program like this:
class ResizableCanvas(Canvas):
def __init__(self, parent, **kwargs):
Canvas.__init__(self, parent, **kwargs)
self.bind("<Configure>", self.on_resize)
self.height = self.winfo_reqheight()
self.width = self.winfo_reqwidth()
def on_resize(self, event):
wscale = float(event.width) / self.width
hscale = float(event.height) / self.height
self.width = event.width
self.height = event.height
self.config(width=self.width, height=self.height)
class ViewerTab:
def __init__(self, master, model, dim=800):
self.sideFrame = ttk.Frame(self.master, width=100)
self.coords = {"x":0,"y":0,"x2":0,"y2":0}
self.lines = []
def click(self):
self.coords["x"] = self.x
self.coords["y"] = self.y
self.lines.append(self.canvas.create_line(self.coords["x"],self.coords["y"],self.coords["x"],self.coords["y"]))
def drag(self):
# update the coordinates from the event
self.coords["x2"] = self.x
self.coords["y2"] = self.y
self.canvas.coords(self.lines[-1], self.coords["x"],self.coords["y"],self.coords["x2"],self.coords["y2"])
#self.canvas.bind("<Button-1>", self.dirbutton)
#self.canvas.bind("<B1-Motion>", self.move)
self.canvas.bind("<ButtonRelease-1>", self.nomove)
self.canvas.bind("<Button-2>", self.get_position)
self.canvas.bind("<ButtonPress-1>", self.click)
self.canvas.bind("<B1-Motion>", self.drag)
So if I got it right from the comments, the issue is to be able to both pan the slide and draw on it using binding to mouse clicks and motion. There are several way to do that, for instance:
Use radiobuttons so that the user selects the "mode": either pan or annotate. Here is a small example based on https://stackoverflow.com/a/50129744/6415268 for the drawing part. The click() and drag() functions do different actions depending on the selected mode (stored a the StringVar).
import tkinter as tk
coords = {"x": 0, "y": 0, "x2": 0, "y2": 0}
# keep a reference to all lines by keeping them in a list
lines = []
def click(event):
if mode.get() == "pan":
canvas.scan_mark(event.x, event.y)
else:
# define start point for line
coords["x"] = canvas.canvasx(event.x)
coords["y"] = canvas.canvasy(event.y)
# create a line on this point and store it in the list
lines.append(canvas.create_line(coords["x"], coords["y"], coords["x"], coords["y"]))
def drag(event):
if mode.get() == "pan":
canvas.scan_dragto(event.x, event.y, gain=1)
else:
# update the coordinates from the event
coords["x2"] = canvas.canvasx(event.x)
coords["y2"] = canvas.canvasy(event.y)
# Change the coordinates of the last created line to the new coordinates
canvas.coords(lines[-1], coords["x"], coords["y"], coords["x2"], coords["y2"])
root = tk.Tk()
mode = tk.StringVar(root, "pan")
toolbar = tk.Frame(root)
toolbar.pack(fill='x')
tk.Radiobutton(toolbar, text="Pan",
variable=mode, value="pan").pack(side='left')
tk.Radiobutton(toolbar, text="Annotate",
variable=mode, value="annotate").pack(side='left')
canvas = tk.Canvas(root, bg="white")
canvas.create_rectangle(0, 0, 50, 50, fill='red')
canvas.create_rectangle(400, 400, 450, 450, fill='blue')
canvas.pack(fill='both')
canvas.bind("<ButtonPress-1>", click)
canvas.bind("<B1-Motion>", drag)
root.mainloop()
Another possibility is to use different bindings for the two kinds of actions using event modifiers (e.g. pressing the Ctrl, Shift or Alt key). For instance, the panning can be bound to Ctrl + mouse events while the drawing happens on simple mouse clicks and motion.
import tkinter as tk
coords = {"x": 0, "y": 0, "x2": 0, "y2": 0}
# keep a reference to all lines by keeping them in a list
lines = []
def draw_click(event):
# define start point for line
coords["x"] = canvas.canvasx(event.x)
coords["y"] = canvas.canvasy(event.y)
# create a line on this point and store it in the list
lines.append(canvas.create_line(coords["x"], coords["y"], coords["x"], coords["y"]))
def draw_drag(event):
# update the coordinates from the event
coords["x2"] = canvas.canvasx(event.x)
coords["y2"] = canvas.canvasy(event.y)
# Change the coordinates of the last created line to the new coordinates
canvas.coords(lines[-1], coords["x"], coords["y"], coords["x2"], coords["y2"])
root = tk.Tk()
toolbar = tk.Frame(root)
toolbar.pack(fill='x')
canvas = tk.Canvas(root, bg="white")
canvas.create_rectangle(0, 0, 50, 50, fill='red')
canvas.create_rectangle(400, 400, 450, 450, fill='blue')
canvas.pack(fill='both')
canvas.bind("<ButtonPress-1>", draw_click)
canvas.bind("<B1-Motion>", draw_drag)
canvas.bind('<Control-ButtonPress-1>', lambda event: canvas.scan_mark(event.x, event.y))
canvas.bind("<Control-B1-Motion>", lambda event: canvas.scan_dragto(event.x, event.y, gain=1))
root.mainloop()
I do not know if my question is a stupid one or a tricky one.
So, using Tkinter and Canvas, I succeed to implement a scrolling/zooming function that work perfectly, thanks to this post Move and zoom a tkinter canvas with mouse. I also add a binding to resize the canvas size when the window size change without trouble.
Using coords and after, I have no trouble to move object around.
The trouble came when I tried to combine everything.
Moving and scrolling : no trouble
Scrolling and Zooming : ok
Zooming, moving and scrolling : do not work
The code bellow reproduce the trouble (python 2.7, work on windows). For what I can see, the trouble come from the scaling, maybe caused by the change of coords of the objects, that induce the canvas resizing, and then disable the scaling? If it is the case, I need help to solve this issue. If it is not the case, I need help to found the issue...
By removing/disable the line self.master.after(50, self.Display), moving do no occur anymore.
import Tkinter as tk
import math
class Example:
def __init__ (self, master):
self.master = master
self.interval = 0
self.SizeX, self.SizeY = master.winfo_width(), master.winfo_height()
#Canvas Frame
self.SystemCanvasFrame = tk.Frame(master, bg='black')
self.SystemCanvasFrame.grid(row=0, column=0)
#Canvas
self.SystemCanvas = tk.Canvas(self.SystemCanvasFrame, width=int(self.SizeX*0.75)-20, height=self.SizeY-20, bg="black")
self.SystemCanvas.focus_set()
self.xsb = tk.Scrollbar(self.SystemCanvasFrame, orient="horizontal", command=self.SystemCanvas.xview)
self.ysb = tk.Scrollbar(self.SystemCanvasFrame, orient="vertical", command=self.SystemCanvas.yview)
self.SystemCanvas.configure(scrollregion=(-500,-500,500,500))
self.SystemCanvas.configure(yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set)
#add the canvas with scroll bar in grid format
self.xsb.grid(row=1, column=0, sticky="ew")
self.ysb.grid(row=0, column=1, sticky="ns")
self.SystemCanvas.grid(row=0, column=0, sticky="nsew")
# This is what enables using the mouse to slide the window:
self.SystemCanvas.bind("<ButtonPress-1>", self.move_start)
self.SystemCanvas.bind("<B1-Motion>", self.move_move)
#windows scroll
self.SystemCanvas.bind("<MouseWheel>",self.zoomer)
#resize the main window
self.master.bind('<Configure>', self.UpdateCanvasSize)
#Create Objects
self.Size = 5 #object Size
x0 = 0
y0 = 0
x1 = self.Size
y1 = self.Size
self.SystemCanvas.create_oval(x0,y0,x1,y1, fill='green', outline='green', width=3, tags='Green')
self.SystemCanvas.create_oval(x0,y0,x1,y1, fill='red', outline='red', width=3, tags='Red')
self.SystemCanvas.create_oval(x0,y0,x1,y1, fill='yellow', outline='yellow', width=1, tags='Yellow')
self.Display()
def Display(self):
self.interval += 0.5 #speed parameter
GreenPos = self.UpdatePosition(0.1*self.interval, (0,0), 50)
RedPos = self.UpdatePosition(0.02*self.interval+180, (0,0), 200)
YellowPos = self.UpdatePosition(0.3*self.interval, RedPos, 10)
self.MoveObject('Green', GreenPos)
self.MoveObject('Red', RedPos)
self.MoveObject('Yellow', YellowPos)
self.master.after(50, self.Display) #Disable to zoom
def MoveObject (self, Obj, pos): #only move object that are in the field of view
"""Move Obj to the given position (tuple - xy)"""
ID = self.SystemCanvas.find_withtag(Obj)
#Convert the Center of the object to the coo need for tk
x0 = pos[0] - self.Size/2.0 #radius of the circle
y0 = pos[1] - self.Size/2.0
x1 = pos[0] + self.Size/2.0
y1 = pos[1] + self.Size/2.0
self.SystemCanvas.coords(ID, x0,y0,x1,y1)
def UpdatePosition(self, angle, center, distance):
"""Calculate next object position around the Center at the Distance and speed determine by Angle (in Radian) - Center of the object"""
h = center[0]
k = center[1]
radius = distance
Rad = angle
x = h+radius*math.cos(Rad)
y = k+radius*math.sin(Rad)
return (x, y)
def UpdateCanvasSize(self, event):
"""Permit to resize the canvas to the window"""
self.SizeX, self.SizeY = self.master.winfo_width(), self.master.winfo_height()
self.SystemCanvas.config(width=int(self.SizeX*0.75)-20, height=self.SizeY-20)
def move_start(self, event):
"""Detect the beginning of the move"""
self.SystemCanvas.scan_mark(event.x, event.y)
self.SystemCanvas.focus_set() #security, set the focus on the Canvas
def move_move(self, event):
"""Detect the move of the mouse"""
self.SystemCanvas.scan_dragto(event.x, event.y, gain=1)
def zoomer(self,event):
"""Detect the zoom action by the mouse. Zoom on the mouse focus"""
true_x = self.SystemCanvas.canvasx(event.x)
true_y = self.SystemCanvas.canvasy(event.y)
if (event.delta > 0):
self.SystemCanvas.scale("all", true_x, true_y, 1.2, 1.2)
elif (event.delta < 0):
self.SystemCanvas.scale("all", true_x, true_y, 0.8, 0.8)
self.SystemCanvas.configure(scrollregion = self.SystemCanvas.bbox("all"))
if __name__ == '__main__':
root = tk.Tk()
root.geometry('1125x750')
app = Example(root)
root.mainloop()
I'm new to Tkinter so this might not be the most elegant solution but I hope it gives you an idea on how to solve the problem.
The zoomer method scales your coordinates but these coordinates are reset anytime you call MoveObject or UpdatePosition. I added code that keeps track of the scale factor, self.scale, and a method update_coord that scales a given coordinate based on the scale factor. Finally, I called update_coord in the MoveObject and UpdatePosition methods.
Here is the working code;
import Tkinter as tk
import math
class Example:
def __init__ (self, master):
self.scale = 1 #Added
self.master = master
self.interval = 0
self.SizeX, self.SizeY = master.winfo_width(), master.winfo_height()
#Canvas Frame
self.SystemCanvasFrame = tk.Frame(master, bg='black')
self.SystemCanvasFrame.grid(row=0, column=0)
#Canvas
self.SystemCanvas = tk.Canvas(self.SystemCanvasFrame, width=int(self.SizeX*0.75)-20, height=self.SizeY-20, bg="black")
self.SystemCanvas.focus_set()
self.xsb = tk.Scrollbar(self.SystemCanvasFrame, orient="horizontal", command=self.SystemCanvas.xview)
self.ysb = tk.Scrollbar(self.SystemCanvasFrame, orient="vertical", command=self.SystemCanvas.yview)
self.SystemCanvas.configure(scrollregion=(-500,-500,500,500))
self.SystemCanvas.configure(yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set)
#add the canvas with scroll bar in grid format
self.xsb.grid(row=1, column=0, sticky="ew")
self.ysb.grid(row=0, column=1, sticky="ns")
self.SystemCanvas.grid(row=0, column=0, sticky="nsew")
# This is what enables using the mouse to slide the window:
self.SystemCanvas.bind("<ButtonPress-1>", self.move_start)
self.SystemCanvas.bind("<B1-Motion>", self.move_move)
#windows scroll
self.SystemCanvas.bind("<MouseWheel>",self.zoomer)
#resize the main window
self.master.bind('<Configure>', self.UpdateCanvasSize)
#Create Objects
self.Size = 5 #object Size
x0 = 0
y0 = 0
x1 = self.Size
y1 = self.Size
self.SystemCanvas.create_oval(x0,y0,x1,y1, fill='green', outline='green', width=3, tags='Green')
self.SystemCanvas.create_oval(x0,y0,x1,y1, fill='red', outline='red', width=3, tags='Red')
self.SystemCanvas.create_oval(x0,y0,x1,y1, fill='yellow', outline='yellow', width=1, tags='Yellow')
self.Display()
#**Added Method
def update_coord(self, coord):
"""Calculate the scaled cordinate for a given cordinate based on the zoomer scale factor"""
new_coord = [coord_i * self.scale for coord_i in coord]
return new_coord
def Display(self):
self.interval += 0.5 #speed parameter
GreenPos = self.UpdatePosition(0.1*self.interval, (0,0), 50)
RedPos = self.UpdatePosition(0.02*self.interval+180, (0,0), 200)
YellowPos = self.UpdatePosition(0.3*self.interval, RedPos, 10)
self.MoveObject('Green', GreenPos)
self.MoveObject('Red', RedPos)
self.MoveObject('Yellow', YellowPos)
self.master.after(1, self.Display) #Disable to zoom
def MoveObject (self, Obj, pos): #only move object that are in the field of view
"""Move Obj to the given position (tuple - xy)"""
ID = self.SystemCanvas.find_withtag(Obj)
#Convert the Center of the object to the coo need for tk
x0 = pos[0] - self.Size/2.0 #radius of the circle
y0 = pos[1] - self.Size/2.0
x1 = pos[0] + self.Size/2.0
y1 = pos[1] + self.Size/2.0
c_0 = self.update_coord([x0, y0]) #Added
c_1 = self.update_coord([x1, y1]) #Added
self.SystemCanvas.coords(ID, c_0[0], c_0[1], c_1[0], c_1[1]) #Added/Edited
def UpdatePosition(self, angle, center, distance):
"""Calculate next object position around the Center at the Distance and speed determine by Angle (in Radian) - Center of the object"""
h = center[0]
k = center[1]
radius = distance
Rad = angle
x = h+radius*math.cos(Rad)
y = k+radius*math.sin(Rad)
return self.update_coord([x, y]) #Added/Edited
def UpdateCanvasSize(self, event):
"""Permit to resize the canvas to the window"""
self.SizeX, self.SizeY = self.master.winfo_width(), self.master.winfo_height()
self.SystemCanvas.config(width=int(self.SizeX*0.75)-20, height=self.SizeY-20)
def move_start(self, event):
"""Detect the beginning of the move"""
self.SystemCanvas.scan_mark(event.x, event.y)
self.SystemCanvas.focus_set() #security, set the focus on the Canvas
def move_move(self, event):
"""Detect the move of the mouse"""
self.SystemCanvas.scan_dragto(event.x, event.y, gain=1)
def zoomer(self,event):
"""Detect the zoom action by the mouse. Zoom on the mouse focus"""
true_x = self.SystemCanvas.canvasx(event.x)
true_y = self.SystemCanvas.canvasy(event.y)
if (event.delta > 0):
self.SystemCanvas.scale("all", true_x, true_y, 1.2, 1.2)
self.scale *= 1.2 #**Added
elif (event.delta < 0):
self.SystemCanvas.scale("all", true_x, true_y, 0.8, 0.8)
self.scale *= 0.8 #**Added
#self.SystemCanvas.configure(scrollregion = self.SystemCanvas.bbox("all")) #**Removed (This disables scrollbar after zoom)
if __name__ == '__main__':
root = tk.Tk()
root.geometry('1125x750')
app = Example(root)
root.mainloop()
I can track where the user clicks and where they release but I want to track distance traveled.
from Tkinter import *
root = Tk()
class DragCursor():
def __init__(self, location):
self.label = location
location.bind('<ButtonPress-1>', self.StartMove)
location.bind('<ButtonRelease-1>', self.StopMove)
def StartMove(self, event):
startx = event.x
starty = event.y
print [startx, starty]
def StopMove(self, event):
self.StartMove
stopx = event.x
stopy = event.y
print [stopx, stopy]
location = Canvas(root, width = 300, height = 300)
DragCursor(location)
location.pack()
root.mainloop()
You just need to use the distance formula for determining the distance between two points in an xy-plane,
Also, you need to include some kind of instance variable that will save the coordinates for the start and end points so that you can compute it after the mouse release.
This is pretty much your code just with a new distancetraveled function that is printed at the end of StopMove using self.positions.
from Tkinter import *
root = Tk()
class DragCursor():
def __init__(self, location):
self.label = location
location.bind('<ButtonPress-1>', self.StartMove)
location.bind('<ButtonRelease-1>', self.StopMove)
self.positions = {}
def StartMove(self, event):
startx = event.x
starty = event.y
self.positions['start'] = (startx, starty)
def StopMove(self, event):
stopx = event.x
stopy = event.y
self.positions['stop'] = (stopx, stopy)
print self.distancetraveled()
def distancetraveled(self):
x1 = self.positions['start'][0]
x2 = self.positions['stop'][0]
y1 = self.positions['start'][1]
y2 = self.positions['stop'][1]
return ((x2-x1)**2 + (y2-y1)**2)**0.5
location = Canvas(root, width = 300, height = 300)
DragCursor(location)
location.pack()
root.mainloop()
Can anyone tell me why my 'addLine' method is failing to call when the self.rec button is clicked?
from tkinter import *
from tkinter import ttk
root = Tk()
class Paint:
def __init__(self, parent):
self.parent = parent
self.whiteBoard = Canvas(self.parent)
self.whiteBoard.grid(column=0, row=0, sticky=(N,W,E,S))
self.lastx = 0
self.lasty = 0
self.rec = self.whiteBoard.create_rectangle((10, 10, 30, 30), fill="red")
self.whiteBoard.tag_bind(self.rec, "<Button-1>", self.getClick)
def xy(self, event):
self.lastx, self.lasty = event.x, event.y
print (event.x, " is the x coordinate")
print (event.y, " is the y coordinate")
def addLine(self, event):
canvas.create_line((lastx, lasty, event.x, event.y))
self.lastx, self.lasty = event.x, event.y
def getClick(self, event):
self.whiteBoard.bind("<Button-1>", self.xy)
self.whiteBoard.bind("B1-Motion>", self.addLine)
white = Paint(root)
root.mainloop()
This is all part of an attempt to make a MS paint clone using Tkinter.
First, you are binding to B1-Motion> (note the missing <). But, more importantly, don't do bindings like those. It looks like the getClick method is actually "select the line tool". Then, add the bindings <Button-1> and <B1-Motion> to the canvas itself. When the callbacks are called, you do the actions according to the selected tool.
Here is rough sketch that follows this suggestion (with a RECTANGLE tool as a bonus):
import tkinter
# TOOLS
LINE, RECTANGLE = list(range(2))
class Paint:
def __init__(self, canvas):
self.canvas = canvas
self._tool, self._obj = None, None
self.lastx, self.lasty = None, None
self.canvas.bind('<Button-1>', self.update_xy)
self.canvas.bind('<B1-Motion>', self.draw)
def draw(self, event):
if self._tool is None or self._obj is None:
return
x, y = self.lastx, self.lasty
if self._tool in (LINE, RECTANGLE):
self.canvas.coords(self._obj, (x, y, event.x, event.y))
def update_xy(self, event):
if self._tool is None:
return
x, y = event.x, event.y
if self._tool == LINE:
self._obj = self.canvas.create_line((x, y, x, y))
elif self._tool == RECTANGLE:
self._obj = self.canvas.create_rectangle((x, y, x, y))
self.lastx, self.lasty = x, y
def select_tool(self, tool):
print('Tool', tool)
self._tool = tool
class Tool:
def __init__(self, whiteboard, parent=None):
self.whiteboard = whiteboard
frame = tkinter.Frame(parent)
self._curr_tool = None
for i, (text, t) in enumerate((('L', LINE), ('R', RECTANGLE))):
lbl = tkinter.Label(frame, text=text, width=2, relief='raised')
lbl._tool = t
lbl.bind('<Button-1>', self.update_tool)
lbl.pack(padx=6, pady=6*(i % 2))
frame.pack(side='left', fill='y', expand=True, pady=6)
def update_tool(self, event):
lbl = event.widget
if self._curr_tool:
self._curr_tool['relief'] = 'raised'
lbl['relief'] = 'sunken'
self._curr_tool = lbl
self.whiteboard.select_tool(lbl._tool)
root = tkinter.Tk()
canvas = tkinter.Canvas(highlightbackground='black')
whiteboard = Paint(canvas)
tool = Tool(whiteboard)
canvas.pack(fill='both', expand=True, padx=6, pady=6)
root.mainloop()