Drawing items on the canvas

Objects added to the canvas are called items. New items are added to the canvas using different create methods such as create_line, create_arc, create_ovalcreate_rectangle, create_polygon, create_text, create_bitmap, and create_image.

Items added to the canvas are placed in a stack. New items are added on top of items already on the canvas. Every time you add an item using one of the various create methods, it returns a unique item handle or an item ID that is a unique integer. This item handle can be used to refer to and manipulate the added item.

In addition to an item handle, items can have the following item specifiers:

  • tags are specifiers that we can add to one or more items
  • ALL (or the string all) matches all items on the canvas
  • CURRENT (or current) matches the item under the mouse pointer if any

We can use any of the preceding item specifiers for methods that act on canvas items.

To add a tag to an item, you specify the tag (which is a string) as its configurable option, either at the time of creating the object or later using the itemconfig method or the addtag_withtag method, as follows:

canvas.create_rectangle(10, 10, 50, 50, tags="foo")
canvas.itemconfig(item_specifier, tags="spam")
canvas.addtag_withtag("spam", "baz")

You can add multiple tags to an item at once by passing in the tags as a tuple of strings, as follows:

canvas.itemconfig(item_specifier, tags=("tag_A", "tag_B"))

To get all tags associated with an item handle, use gettags as follows:

canvas.gettags(item_handle)

This returns a tuple of all tags associated with that item handle.

To get the item handles for all items that have a given tag, use find_withtag:

canvas.find_withtag("spam")

This returns a tuple of item handles for all items with a tag of spam.

Given this information, let's code the functionality for the first six buttons, as shown in the following screenshot:

More specifically, we will code the functionality for the following function names that we have already defined earlier in the tuple tool_bar_functions:  "draw_line", "draw_oval", "draw_rectangle", "draw_arc", and "draw_triangle", "draw_star"


Here's the code for draw_ line (see code 6.04.py):

def draw_line(self):
self.current_item = self.canvas.create_line(self.start_x,
self.start_y, self.end_x,
self.end_y, fill=self.fill, width=self.width, arrow=self.arrow,
dash=self.dash)

This uses the create_line method and draws a line from the start x, y coordinates to the end x, y coordinates. We have defined four new attributes for handling four different properties of the line:

  • fill: Line color. Default is black, initialized as red in our program.

  • width: Default is 1, initialized as 2 in our program.

  • arrow: Default is None. The available choices are: None, First, Last, Both.

  • dash: A dash pattern, which is a list of segment lengths. Only the odd segments are drawn.

We will later provide options for changing these four values from the top bar and hence these have been added as class attributes.

Also note that since create_line (and all create methods) return the item handle for the created item, we store it in an attribute named current_item. This gives us access to the last created item, which we will soon put to good use.

Next, here's the code for draw_ oval (see code 6.04.py):

def draw_oval(self):
self.current_item = self.canvas.create_oval(self.start_x,
self.start_y, self.end_x,
self.end_y, outline=self.outline, fill=self.fill,width=self.width)

This is identical to the code for draw_line, except that we added a new attribute named outline that takes care of the outline color.

We will not discuss the code for create_rectangle and create_arc, which are almost identical to the code of draw_oval discussed here (see code 6.04.py).

Let's now discuss the create_polygon method. This method can be used to create all sorts of interesting shapes. Let's begin with the simple case of drawing an equilateral triangle (see code 6.04.py):

def draw_triangle(self):
dx = self.end_x - self.start_x
dy = self.end_y - self.start_y
z = complex(dx, dy)
radius, angle0 = cmath.polar(z)
edges = 3
points = list()
for edge in range(edges):
angle = angle0 + edge * (2 * math.pi) / edges
points.append(self.start_x + radius * math.cos(angle))
points.append(self.start_y + radius * math.sin(angle))
self.current_item = self.canvas.create_polygon(points,
outline=self.outline,
fill=self.fill, width=self.width)

The preceding code first converts the changes in the x, y coordinates from the Cartesian coordinate system to the polar coordinates represented by an angle and a radius. It then calculates the x, y coordinates for all three edges of the triangle using the following formula:

x = r*cosσ and y = r*sinσ 

Once we have the x, y coordinates for all three vertices of the triangle, we call the create_polygon method to draw the triangle.
Let's now use the create_polygon method to make stars. A star (and many other polygons) can be thought of as a collection of points or spokes on two concentric circles, as shown in the following figure:

The star shown in the preceding figure has five spokes. We will later allow the user to change the number of spokes. Therefore, let's start by defining a class attribute as follows:

number_of_spokes = 5

The shape of the star is also determined by the ratio of the radius of the inner circle to the radius of the outer circle, as in the preceding figure. This is called the spoke ratio. This ratio is 2 for a standard star. Changing this ratio can also produce all sorts of interesting star shapes. However, we will keep it at 2 for our example. Given these rules, the code for draw_star is defined as follows (see code 6.04.py):


def draw_star(self):
dx = self.end_x - self.start_x
dy = self.end_y - self.start_y
z = complex(dx, dy)
radius_out, angle0 = cmath.polar(z)
radius_in = radius_out / 2 # this is the spoke ratio
points = list()
for edge in range(self.number_of_spokes):
# outer circle angle
angle = angle0 + edge * (2 * math.pi) / self.number_of_spokes
# x coordinate (outer circle)
points.append(self.start_x + radius_out * math.cos(angle))
# y coordinate (outer circle)
points.append(self.start_y + radius_out * math.sin(angle))
# inner circle angle
angle += math.pi / self.number_of_spokes
# x coordinate (inner circle)
points.append(self.start_x + radius_in * math.cos(angle))
# y coordinate (inner circle)
points.append(self.start_y + radius_in * math.sin(angle))
self.current_item = self.canvas.create_polygon(points, outline=self.outline, fill=self.fill, width=self.width)

The preceding code is heavily commented for you to understand. This is very similar to the code we used to draw triangles.

Now, instead of having points on one circle (as for triangles), we have points on two circles. We again use the same technique to first convert the x, y coordinates from mouse events to polar coordinates. Once we have the polar coordinates, it is easy to move the points in the circle.

We then move the points by a given angle and change back to Cartesian coordinates. We keep appending all the points to an empty list called points. Once we have all the points, the last line calls the create_polygon method of the canvas object to draw the star.
Now we have all the methods to create these six shapes. But they need to be called from somewhere for the drawing to happen. And we have already decided that they would be called dynamically.

Accordingly, we define a method, execute_selected_method, which takes the string for the selected toolbar function, converts the string into a callable function, and executes it dynamically.

The code is as follows (see code 6.04.py):

def execute_selected_method(self):
self.current_item = None
func = getattr(self, self.selected_tool_bar_function,
self.function_not_defined)
func()

This method, getattr, provides a reference to a method from the given string at runtime. A second argument provides a fallback mechanism whereby if the method object from the first argument is not found, a reference to the second method is provided.

This helps us gracefully handle situations where a dynamically created method does not exist. We simply define the fallback method as an empty method to handle those cases (see code 6.04.py):

def function_not_defined(self):
pass

So now we have a method to execute the selected method dynamically. Where do we plug in this method?

Since the drawing must begin when the mouse is clicked, we call the execute_selected_method method once from the on_mouse_button_pressed method.

The drawing must continue while the mouse is dragged in a clicked position. So we call this method again from the on_mouse_button_pressed_motion method.

However, although we want to keep the last drawn object during the mouse motion, we want to remove all other items except for the last drawn item. We therefore modify on_mouse_button_pressed_motion as follows (see code 6.04.py):

def on_mouse_button_pressed_motion(self, event):
self.end_x = self.canvas.canvasx(event.x)
self.end_y = self.canvas.canvasy(event.y)
self.canvas.delete(self.current_item)
self.execute_selected_method()

Now, if you run 6.04.py, the top six buttons on the toolbar should function as shown in the following screenshot:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.104.183