python tkinter - track and save mousecklicks to build canvas items


python tkinter - track and save mousecklicks to build canvas items



I programmed a method like this on a canvas:



My question: how do I code, that python should wait for the second click, and look if its another circle too? And if so, how can I draw a line between them?


def newKnotornewEdge(event):
if self.state == 0:
self.canvas.create_oval(event.x-25,event.y-25,event.x+25, event.y+25, fill="blue")
self.canvas.create_text(event.x, event.y, text="A", fill="white")
elif self.state == 1:
if self.canvas.itemcget(self.canvas.find_overlapping(event.x, event.y, event.x, event.y), "fill") == "blue":
self.selected = 1
start_x = event.x
start_y = event.y

else:
self.selected = None

if self.selected == 1 and #second mouseclick is a circle too:
#draw line that connects those circels

self.canvas.bind("<Button -1>", newKnotornewEdge)




1 Answer
1



The following canvas screenshot shows the app with 8 circles drawn; 2 and 4 of them are joined with lines; the red circle has been selected (this is what the color red indicates), and is ready to be joined to another circle, on the next click; one circle is not joined and not selected.



a click on the button selects the action 'draw a circle', and purges the circles that may have already been selected.
when this action is active, a click on the canvas draws a blue circle.



When clicked, it activates the action 'join circle' and purges the circles that may have already been selected.
The first click on a circle on the canvas selects this circle whose color changes to red; the second click on a circle on canvas selects the second circle, joins the two with a line, resets the colors to blue, and purges the selection.
A click on an empty part of the canvas does nothing.



enter image description here



You will need to keep track of which action to perform: draw a new circle, or select two circles to join. You also need to keep track of how many circles have been selected.
Then, after successfully drawing a line between circles, you will need to purge the selection.



I chose to use the "functions as a first class object" capability of python to avoid messy state accounting: you select the action to be performed by clicking the relevant button, then, clicks on the canvas will be related to this action.



The following code does that on the canvas, and prints which action is selected, and which is performed in the console:


import tkinter as tk


class CommandButtons(tk.Frame):
def __init__(self, master):
self.master = master
super().__init__(self.master)
self.make_circle_btn = tk.Button(root, text='make_circle', command=make_circle)
self.make_circle_btn.pack()
self.join_circles_btn = tk.Button(root, text='join_circles', command=select_circles)
self.join_circles_btn.pack()
self.pack()


class CircleApp(tk.Frame):
def __init__(self, master):
self.master = master
super().__init__(self.master)
self.canvas = tk.Canvas(root, width=600, height=600, bg='cyan')
self.canvas.pack(expand=True, fill=tk.BOTH)
self.pack()


def make_circle():
_purge_selection()
print('cmd make_circle selected')
c_app.canvas.bind('<Button-1>', _make_circle)


def _make_circle(event):
c_app.canvas.create_oval(event.x - 25, event.y - 25, event.x + 25, event.y + 25, fill="blue")


def select_circles():
_purge_selection()
print('cmd join_circles selected')
c_app.canvas.bind('<Button-1>', _select_circles)


def _select_circles(event):
print(f'select circle {event}')
x, y = event.x, event.y
selection = c_app.canvas.find_overlapping(x, y, x, y)
if selection is not None and len(selected_circles) < 2:
selected_circles.append(selection)
c_app.canvas.itemconfigure(selection, fill='red')
if len(selected_circles) == 2:
if all(selected_circles):
_join_circles()
_purge_selection()


def _join_circles():
coordinates =
for item in selected_circles:
x, y = find_center(item)
print(x, y)
coordinates.append(x)
coordinates.append(y)
c_app.canvas.create_line(coordinates)


def _purge_selection():
global selected_circles
for item in selected_circles:
c_app.canvas.itemconfigure(item, fill='blue')
selected_circles =


def find_center(item):
x0, y0, x1, y1 = c_app.canvas.bbox(item)
return (x0 + x1) / 2, (y0 + y1) / 2


if __name__ == '__main__':
selected_circles =

root = tk.Tk()
command_frame = CommandButtons(root)
c_app = CircleApp(root)

root.mainloop()



A better version would use a class to encapsulate the selected items; here I used a collection as global variable. It would also properly handle overlapping circle selection.






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

api-platform.com Unable to generate an IRI for the item of type

How to set up datasource with Spring for HikariCP?

PHP contact form sending but not receiving emails