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