Chapter 2:
Use a model

Drawing the same shapes every time in the reset handler doesn’t allow for any variety. You would normally use a model, a collection of information about what should be drawn. That way you can modify the model and have the reset handler draw those modifications.

Step 1 - Creating the model

A list of dictionaries works well for the model. In this step we’ll define what our model will contain so that we can recreate the square and circle from before.

Define the model in the __init__ function of your Form:

    def __init__(self, **properties):
        # Set Form properties and Data Bindings.
        self.init_components(**properties)

        self.model = [
            {'type': 'square', 'x': 100, 'y': 100, 'width': 50, 'stroke': True, 'fill': False},
            {'type': 'circle', 'x': 300, 'y': 100, 'radius': 50, 'stroke': True, 'fill': True},
        ]

        self.canvas_size = 500
        self.canvas_1.height = self.canvas_size
        self.canvas_1.reset_context()  

The type field in the dictionary will tell us what type of shape to draw, while the other fields give information needed for that type of shape.

Step 2 - Using the model in the reset handler

Now that we have a model, the reset handler should draw based on that model. Change your reset handler to use the following code:

    def canvas_1_reset(self, **event_args):
        # Adjust these coordinates if you want the drawing area to not be centered
        self.canvas_offset = (self.canvas_1.get_width() - self.canvas_size)/2
        self.canvas_1.translate(self.canvas_offset, 0)

        # Restrict drawing to the section that we want visible
        self.canvas_1.begin_path()
        self.canvas_1.move_to(0, 0)
        self.canvas_1.line_to(self.canvas_size, 0)
        self.canvas_1.line_to(self.canvas_size,self.canvas_size)
        self.canvas_1.line_to(0, self.canvas_size)
        self.canvas_1.close_path()        
        self.canvas_1.clip()

        for shape in self.model:
            if shape['type'] == 'square':
                self.canvas_1.begin_path()
                self.canvas_1.move_to(shape['x'], shape['y'])
                self.canvas_1.line_to(shape['x']+shape['width'],shape['y'])
                self.canvas_1.line_to(shape['x']+shape['width'],shape['y']+shape['width'])
                self.canvas_1.line_to(shape['x'], shape['y']+shape['width'])
                self.canvas_1.close_path()

            if shape['type'] == 'circle':
                self.canvas_1.begin_path()
                self.canvas_1.arc(shape['x'], shape['y'], shape['radius'])
                self.canvas_1.close_path()

            if shape['stroke']:
                self.canvas_1.stroke()  

            if shape['fill']:
                self.canvas_1.fill()

This loops over every shape in self.model and executes the correct Canvas drawing functions for that shape.

Step 3 - Adding to the Canvas via the user interface

Now that we have a model and a reset handler that draws from that model, we can add in a user interface that allows the user to add shapes to the Canvas. We’ll add the shapes with random coordinates.

Add a FlowPanel below the Canvas and add two Buttons to it, add_square and add_circle. Create the click handler functions for both by using the Object Palette.

Adding Buttons to create squares and circles on the Canvas

Adding Buttons to create squares and circles on the Canvas

Now in the click handlers for those Buttons, add the code to add the shapes at random positions. First we’ll need to import random at the top of the code:

from ._anvil_designer import CanvasPlaygroundTemplate
from anvil import *
import random

And then in the click handlers themselves, we can add to the self.model list.

    def add_square_click(self, **event_args):
        x = random.randint(0,self.canvas_size-50)
        y = random.randint(0,self.canvas_size-50)
        self.model.append({
            'type': 'square',
            'x': x,
            'y': y,
            'width': 50,
            'stroke': True,
            'fill': False
        })
        self.canvas_1.reset_context()

    def add_circle_click(self, **event_args):
        x = random.randint(0,self.canvas_size-50)
        y = random.randint(0,self.canvas_size-50)
        self.model.append({
            'type': 'circle',
            'x': x,
            'y': y,
            'radius': 50,
            'stroke': True,
            'fill': False
        })
        self.canvas_1.reset_context()

Remember that anytime the Canvas needs redrawn, we must call reset_context(). This includes after modifying the model in any way.

Now run the app and click the Buttons to add shapes to the Canvas.

We’ve now learned how to draw shapes on the Canvas. Next, we’ll look at how to draw images.

Chapter complete

Congratulations, you've completed this chapter!