Canvas

The Canvas library, as used on the course so far, is a thin "wrapper" over the Canvas module of the Tkinter library for Python.

A "wrapper" is a set of functions that make one piece of code (typically a library) easier to use. A wrapper might be used to make a library from one programming language available in another, or (as in this case) to make a verbose library simpler to use.

For our purposes, it's important to remember that the Canvas library as it has been used so far is only a wrapper on a more fully feature Canvas module. If we can't find the functionality we need in this wrapper, we may need to understand the underlying module in order to extend the wrapper, to give us access to that functionality.

You can download the Canvas library on its own here

Features of the Canvas library

The canvas library makes available a number of common shapes, which can be drawn using plain function calls. This has been done to hide the underlying details, which include object oriented method calls. The functions made available are the following:

  • create_rectangle(x1, y1, x2, y2)
  • create_arc(x1, y1, x2, y2)
  • create_line(x1, y1, x2, y2)
  • create_oval(x1, y1, x2, y2)
  • create_text(x1, y1)

In each case the "x" and "y" values represent co-ordinates for use in the functions. Each function also takes a number of optional arguments, that are added at the end as keyword arguments. For example, using the create_text() function:

create_text(100, 100, text="Hello World")

The keyword argument "text" is given the value "Hello World".

More keyword arguments

The shape drawing functions exposed by the Canvas library are very thin wrappers on the underlying Canvas module shape functions described here. Any keyword arguments are passed down to these functions. There are quite a few for each shape - far more than we have talked about in the course. You can read the link above and play around to work out what some of them are, but here are a few of them:

The "activefill" argument

This makes the shape change colour when you move the mouse over it:

create_arc(10, 10, 100, 100, activefill="blue")

Also available for the other shapes.

The "width" argument

Changes the width of the "pen" used to draw the shape so:

create_rectangle(50, 50, 100, 100, width=5)

draws a rectangle with a thick line.

The width argument also works with the create_text() function, but in this case it limits the width of the string, in pixels:

create_text(100, 100, text="This is a long piece of text", width=100)

This will cause the text to wrap after the word "long".

The "arrow" argument

Used with a line, this adds an arrow to the beginning ("first") or end ("last") of a line, or both ("both").

create_line(50, 50, 100, 100, arrow="first")

There is also an argument to change the shape of the arrow. It's documented here. See if you can figure out how to use it.

Keyword argument error messages

To use a keyword argument, you have to know what keyword to use and what values it accepts. Here are the errors you can expect if you don't get this right:

create_line(50, 50, 100, 100, blargh="hello")

Traceback (most recent call last):
  File "./tester.py", line 5, in ?
    create_line(50, 50, 100, 100, blargh="hello")
  File "/users/fda/pzs/teaching/canvas/Canvas.py", line 138, in create_line
    return _getCanvas().create_line( x1, y1, x2, y2, kw )
  File "/users/fda/pzs/teaching/canvas/Canvas.py", line 29, in create_line
    r = self._canvas.create_line( x1, y1, x2, y2, kw)
  File "/usr/lib/python2.4/lib-tk/Tkinter.py", line 2090, in create_line
    return self._create('line', args, kw)
  File "/usr/lib/python2.4/lib-tk/Tkinter.py", line 2076, in _create
    return getint(self.tk.call(
_tkinter.TclError: unknown option "-blargh"
create_rectangle(50, 50, 100, 100, fill="hello")

Traceback (most recent call last):
  File "./tester.py", line 5, in ?
    create_line(50, 50, 100, 100, fill="hello")
  File "/users/fda/pzs/teaching/canvas/Canvas.py", line 138, in create_line
    return _getCanvas().create_line( x1, y1, x2, y2, kw )
  File "/users/fda/pzs/teaching/canvas/Canvas.py", line 29, in create_line
    r = self._canvas.create_line( x1, y1, x2, y2, kw)
  File "/usr/lib/python2.4/lib-tk/Tkinter.py", line 2090, in create_line
    return self._create('line', args, kw)
  File "/usr/lib/python2.4/lib-tk/Tkinter.py", line 2076, in _create
    return getint(self.tk.call(
_tkinter.TclError: unknown color name "hello"
create_line(50, 50, 100, 100, width="hello")

Traceback (most recent call last):
  File "./tester.py", line 5, in ?
    create_line(50, 50, 100, 100, width="hello")
  File "/users/fda/pzs/teaching/canvas/Canvas.py", line 138, in create_line
    return _getCanvas().create_line( x1, y1, x2, y2, kw )
  File "/users/fda/pzs/teaching/canvas/Canvas.py", line 29, in create_line
    r = self._canvas.create_line( x1, y1, x2, y2, kw)
  File "/usr/lib/python2.4/lib-tk/Tkinter.py", line 2090, in create_line
    return self._create('line', args, kw)
  File "/usr/lib/python2.4/lib-tk/Tkinter.py", line 2076, in _create
    return getint(self.tk.call(
_tkinter.TclError: bad screen distance "hello"

The "complete" function

The complete function sets the title of the window, including an event handler function to quit the window when a button is pressed, and launches Tkinter to draw the shapes you have chosen. Again, the complete function has been designed to hide many of the messy details underneath the Canvas library.

Customising the Canvas library

Trying to customise the Canvas library is an excellent programmer's exercise, because you will be basing your changes on established code so you can use that to provide examples. Obviously, taking a backup of the Canvas library is a sensible precaution.

Trying to understand the Canvas library file

Open up the Canvas library in IDLE and scroll through it. A lot of it won't make much sense to you, but the key is to find the bits that do make sense and try to work out what they do. For example, at the bottom of the file is a comment that says:

##########################################################
# These are the only visible functions out of the module

below here are the functions that are exposed, looking a bit like this:

def create_rectangle( x1, y1, x2, y2, **kw ):
    return _getCanvas().create_rectangle( x1, y1, x2, y2, kw )

All the shape functions look a bit like this. We may not know what the _getCanvas() function does, but we can see that this pattern of call is repeated in all the other shape functions.

Changing the size of the window

According to this page, the size of the window is set when the "Canvas" object is created, using the keyword arguments "width" and "height". We just need to find where in the Canvas library file this object is created. Try searching through the file for (case sensitive "Canvas".

The object is created on line 85 with the line:

_canvas = Canvas( _root, background = "white" )

It already has a keyword argument, so we can experiment by adding our own (it might not work, but then we can always change it backā€¦):

_canvas = Canvas( _root, background = "white", width=500, height=500)

This seems to work! However, using raw numbers like this is a rather ugly solution. We should consider adding constant values at the top of the file, or perhaps adding arguments to the complete() function, so that we can set the window size when we call complete(). This is left as an exercise for the reader. Do ku.ca.alg.scd|szp#hcuot ni teg if you get stuck.

Adding a new wrapper function

This pattern should allow us to wrap a function from the underlying Canvas module that is not already wrapped: the create_polygon() function. First of all, let's copy one of the existing wrapper functions and change it to fit the arguments for the polygon:

def create_polygon( coords, **kw ):
    return _getCanvas().create_polygon( coords, kw )

I've used an argument "coords", like in the documentation found here. If I now create a piece of code to use my new function:

from Canvas import *
create_polygon([10, 10, 50, 50, 50, 100])
complete()

It's not clear at the moment what the polygon function means by "coords", so I've tried providing a list of numbers. If I run this code, I get this error message:

Traceback (most recent call last):
  File "./tester.py", line 5, in ?
    create_polygon([10, 10, 50, 50, 50, 100])
  File "/users/fda/pzs/teaching/canvas/Canvas.py", line 136, in create_polygon
    return _getCanvas().create_polygon( coords, kw )
AttributeError: Can instance has no attribute 'create_polygon'

So I must be missing something else.

If you scroll to the top of the file, you'll see another set of functions with familiar names, inside a "class" called "RawCanvas". For our purposes, we don't need to worry too much about what the syntax of this means, we can simply look underneath "RawCanvas" and see that there is another list of functions corresponding to those that have been wrapped. Again, let's take this pattern and insert some code at this point:

def create_polygon( self, coords, *kw ):
        r = self._canvas.create_polygon( coords, kw )
        self._canvas._root().update()
        return r

This code looks exactly like the RawCanvas call to create_rectangle, but I've changed the relevant pieces to refer to create_polygon instead. If we run our code from before, it now works!

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License