Title: Let the user select tools to scribble on the program in Python and tkinter
This example mostly follows from previous examples:
See those examples for many of the details. This post explains how the program builds and manages the tool buttons that let you draw a scribble or oval. There are four main pieces to this code: the make_tools method, the select_tool method, the mouse event handlers that execute while the user is drawing, and the code that gets the current drawing parameters.
The make_tools Method
The following make_tools method creates the tool buttons.
def make_tools(self):
separator = ttk.Separator(self.toolbar, orient=tk.VERTICAL)
separator.pack(side=tk.LEFT, padx=self.tool_padx, pady=self.tool_pady, fill=tk.Y)
self.tool_images = {
'tool_arrow': tk.PhotoImage(file='tool_arrow.png'),
'tool_scribble': tk.PhotoImage(file='tool_scribble.png'),
'tool_oval': tk.PhotoImage(file='tool_oval.png'),
}
self.tool_buttons = {}
for name, image in self.tool_images.items():
button = tk.Button(self.toolbar, image=image,
command=lambda bound_name=name: self.select_tool(bound_name))
button.pack(side=tk.LEFT, padx=self.tool_padx, pady=self.tool_pady)
self.tool_buttons[name] = button
# Initially select the arrow.
self.select_tool('tool_arrow')
This method creates all of the tools in the tool part of the toolbar. First, it creates a separator. It sets the orientation to vertical and sets fill to Y so the separator makes a vertical bar to separate the drawing tools from the toolbar items created earlier.
Next the code creates a dictionary holding the tool names and their corresponding PhotoImages. It then creates an empty tool_buttons dictionary to hold the tools' buttons.
The code then loops through the tool_images dictionary. For each tool name/PhotoImage pair, the code creates a button that displays the image. When clicked, each button calls the select_tool method passing it the button's tool name.
After creating the button, the program adds it to the tool_buttons list.
After it has created all of the buttons, the method calls the select_tool method described in the following section to initially select the arrow tool.
The select_tool Method
This method selects a tool when the user clicks on its toolbar button.
def select_tool(self, tool_name):
# Save the selected tool.
self.selected_tool = tool_name
# Unbind the mouse events.
self.canvas.unbind('')
self.canvas.unbind('')
self.canvas.unbind('')
# Push the selected button down.
for name, button in self.tool_buttons.items():
if name == tool_name:
button.config(relief=tk.SUNKEN)
else:
button.config(relief=tk.RAISED)
# Set the appropriate cursor.
match tool_name:
case 'tool_arrow':
self.canvas.config(cursor='')
case 'tool_scribble':
self.canvas.config(cursor='cross')
self.canvas.bind('', self.scribble_mouse_down)
self.canvas.bind('', self.scribble_mouse_move)
self.canvas.bind('', self.scribble_mouse_up)
case 'tool_oval':
self.canvas.config(cursor='cross')
self.canvas.bind('', self.oval_mouse_down)
self.canvas.bind('', self.oval_mouse_move)
self.canvas.bind('', self.oval_mouse_up)
This code first saves the newly selected tool's name. It then unbinds the canvas's left mouse button events to uninstall any previously selected tool.
The method then loops through the tool_buttons dictionary. If a button's name matches the tool currently being selected, the code gives it a sunken appearance so it looks pressed. If the button's name does not match the tool being selected, the code gives the button a raised appearance so it looks like it is released. This lets the program treat the drawing buttons as a sort of radio button list so exactly one is always pressed.
The last part of the method uses a match statement to install mouse down, mouse move, and mouse up event handlers for the selected tool. Allowing each tool to have its own set of event handlers makes the code much simpler than it would be if you tried to use a single set of mouse down, mouse move, and mouse up methods to handle every tool.
Mouse Event Handlers
The last drawing-related pieces of the program are the mouse event handlers. The earlier post about the initial scribble program shows how the scribble tool works. The following code shows the oval's mouse down event handler.
def oval_mouse_down(self, event):
# Save the starting point.
self.start_point = (event.x, event.y)
The oval_mouse_down method simply saves the start_point where the mouse was pressed.
The following code shows the mouse move event handler.
def oval_mouse_move(self, event):
# Ignore extraneous events.
if self.start_point is None: return
# If there is a current oval, remove it.
if self.current_oval is not None:
self.canvas.delete(self.current_oval)
# Create an oval.
self.end_point = (event.x, event.y)
thickness, dash_pattern, color = self.get_drawing_parameters()
self.current_oval = self.canvas.create_oval(
self.start_point, self.end_point, outline=self.fg_color,
width=thickness, dash=dash_pattern)
The oval_mouse_move method returns if start_point is None so it can avoid extraneous events.
Next, if current_oval is not None, that means the use is in the middle of drawing an oval and the previous version is visible. In that case, the program deletes it.
The code then saves the mouse's current position in the end_point variable. It gets the current drawing parameters from the get_drawing_parameters method and uses the results to create the new oval.
The following method executes when the user releases the mouse button.
def oval_mouse_up(self, event):
# Ignore extraneous events.
if self.start_point is None: return
# We are no longer creating an oval.
self.start_point = None
self.end_point = None
self.current_oval = None
Like oval_mouse_move, this method returns if start_point is None so it can avoid extraneous events.
It then sets the new oval drawing parameters to None and returns.
At this point, if the user presses the mouse down again, the oval_mouse_down event handler executes and starts the whole thing again.
Getting Drawing Parameters
The following get_drawing_parameters method gets the currently selected drawing parameters.
def get_drawing_parameters(self):
thickness = self.thickness_var.get() + 1
return thickness, self.dash_pattern, self.fg_color
The thickness_var variable is an IntVar bound to the line thickness OptionMenu widget. Because the widget's choices are numbered starting with 0, the code adds 1 to that variable's value to get the current line thickness.
The currently selected dash pattern and color are saved by their tools in variables dash_pattern and fg_color so the method just returns them with the line thickness.
Summary
That explains how the program manages its drawing tools. To add a new tool, follow these steps.
- Modify make_tools so it knows the new tool's name and PhotoImage.
- Modify select_tool so it binds the new shape's mouse event handlers.
- Create the new mouse down, mouse move, and mouse up event handlers.
- In the class's constructor, initialize any required variables. For example, you might set self.current_rectangle = None.
Download the example to see all of the details.
|