Title: Let the user draw polygons in tkinter and Python
My post Let the user draw polygons in Pygame and Python shows how you can let the user draw polygons in Pygame. This post does something similar for tkinter applications.
To use the program, left-click to add points to the selection polyline (a polyline is a series of connected line segments) drawn in red in the picture. After you select some points, right-click to accept the points and define the new polygon. The program draws finished polygons in blue.
The following section briefly explains how the program creates its user interface. The next two sections describe the code that handles mouse events.
User Interface
The main TkDrawPolygonApp class uses the following constructor to build the program's user interface.
def __init__(self):
# Make the main interface.
self.window = tk.Tk()
self.window.title('tk_draw_polygon')
self.window.protocol('WM_DELETE_WINDOW', self.kill_callback)
self.window.geometry('500x400')
# Make the canvas.
self.canvas = tk.Canvas(self.window, background='white',
cursor='crosshair')
self.canvas.pack(padx=5, pady=5, fill=tk.BOTH, expand=True)
# Catch mouse events.
self.canvas.bind('<Button>', self.mouse_down)
self.canvas.bind('<Motion>', self.mouse_move)
# Initially we're not drawing.
self.new_points = self.new_polyline = None
# Display the window.
self.window.focus_force()
self.window.mainloop()
This code initializes tkinter and creates a window. It then makes a Canvas that fills the window.
Next, it binds the canvas to catch mouse down and mouse move events. It looks for <Button> instead of something like <Button-1> so it gets all button press events no matter which button is pressed. Similarly, it looks for <Motion> rather than <Button-1> so it gets mouse move events whether a mouse button is pressed or not.
The code then initializes two drawing variables to None to indicate that we are not currently drawing anything. The new_points list will hold the points that the user has selected for a new polygon. The new_polyline value is the ID of the new polyline the user is drawing while defining a new polygon. You'll see how those variables are used shortly.
After all that setup, the code displays the window.
mouse_down
When the user presses a mouse button down over the program's canvas widget, the following event handler executes.
def mouse_down(self, event):
'''Add this point to the new_points list.'''
# See if this is the left or right button.
if event.num == 1:
# Left mouse button.
# See if we have any points yet.
if self.new_points is None:
# This is the first point.
self.new_points = [(event.x, event.y), (event.x, event.y)]
# Create the selection polyline.
self.new_polyline = self.canvas.create_line(
self.new_points, fill='red', width=3)
else:
# This is not the first point. Add this point.
self.new_points.append((event.x, event.y))
# Update the selection polyline.
self.canvas.coords(self.new_polyline, self.new_points)
elif event.num == 3:
# Right mouse button.
# See if we're drawing.
if self.new_points is None:
return
# Delete the selection polyline.
self.canvas.delete(self.new_polyline)
# Delete the final point.
self.new_points.pop()
# See if we have at least three points.
if len(self.new_points) >= 3:
# Create the final polygon.
self.canvas.create_polygon(self.new_points, outline='blue',
fill='', width=3)
# We are no longer drawing.
self.new_points = self.new_polyline = None
This event handler performs two different tasks depending on whether the user pressed the left or right mouse button.
Left Button Press
If the user pressed the left button, the code checks whether we are currently drawing a new polygon. If we are not drawing a new polygon, it starts doing so by adding the current mouse position to the new_points list twice. The list's first point is the polygon's first point and the last point is always the one that moves when the user moves the mouse. (If that doesn't make sense, run the program and you'll see what I mean.) After it initializes the new_points list, the method creates a polyline and saves its ID in self.new_polyline.
If the user pressed the left mouse button and we have already started drawing the new selection polyline, the code adds the mouse's position to the new_points list. It then calls self.canvas.coords to update the selection polyline's points to show the new point.
Right Button Press
If the user pressed the right mouse button, the code first checks whether we are drawing a new polygon. If we are now drawing a new polygon, the method simply exits.
If we are drawing a polygon, the code deletes the selection polyline. Next, it removes the last point from the new_points list. That was the point that shows the mouse's most recent position as the user selects the next polygon point. You can keep this point in the polygon if you like, but I think removing it is more intuitive. (Give it a try and see which method you prefer.)
After removing the last new point, the code checks whether we have at least three points. If we have three or more points, the code creates the new polygon.
Finally, after creating the new polygon or not, the code sets new_polyline and new_points to None so we know we are no longer drawing a new polygon.
mouse_move
When the user moves the mouse, the program uses the following event handler to update the position of the last point in the selection polyline.
def mouse_move(self, event):
'''Update the last point in the selection polyline.'''
# See if we're drawing.
if self.new_points is None:
return
# Update the last point.
self.new_points[-1] = (event.x, event.y)
self.canvas.coords(self.new_polyline, self.new_points)
First, this method checks whether we are currently drawing a new polygon and it exits if we are not.
If we are drawing a new polygon, the code updates the last point in the new_points list to the mouse's current position and calls self.canvas.coords to update the new_polyline's points.
Conclusion
This example shows how you can let the user draw polygons on a tkinter Canvas widget. What you do with the polygon after that is up to you. For example, you could change its outline and fill colors, use a different line width, or do something behind the scenes without displaying the polygon on the canvas.
Download the example to experiment with it and to see additional details.
|