Python Series Part 25: Canvas Widget - Part 1: The Canvas and Its Drawable Objects

Jarret B

Well-Known Member
Staff member
Joined
May 22, 2017
Messages
458
Reaction score
522
Credits
19,806
We can think of the Canvas widget in Tkinter as similar to a painter's canvas; it lets you place graphical elements on it, such as shapes, but also objects.

For this article, there is a lot to cover. The Canvas widget has a lot of parameters and methods to cover.

Canvas Widget

A Canvas is based on a graphical rendering of objects and drawings. The canvas itself can be scrollable if needed.

To create a Canvas widget, you can give a name to the object. Such as:

Code:
canvas = Canvas()

Within the parentheses, there are various parameters that you can pass to it. The various parameters are:
  • parent - the parent window, this is usually ROOT unless you have multiple windows
  • height - the height of the canvas in pixels
  • width - the width of the canvas in pixels
  • bg - the background color of the canvas
  • bd - border width in pixels
  • highlightbackground - the highlight background color when the canvas does not have focus
  • highlightcolor - color of the canvas' border when it has focus
  • highlightthickness - the thickness, in pixels, of the border
  • relief - the canvas' border - FLAT, RAISED, SUNKEN, GROOVE, RIDGE
  • scrollregion - a tuple defining the canvas' scrollable region (left, top, right, bottom)
  • cursor - specifies the cursor to use when the cursor is within the canvas area
  • confine - determines if the user can scroll the canvas past the viewable area
The 'parent' object is the name of the window in which we place the canvas. In most of my examples, I name the window 'root'.

The 'height' and 'width' of a canvas are based in pixels. It can be the same size as the 'root' window, or any size you wish. Just remember that the size can exceed the 'root' window or be smaller. If larger, then the user may not see parts of the canvas.

If you wish to set the background color, use 'bg' or 'background', and specify a valid color name constant.

We can set the border width in pixels with the 'bd' parameter.

We can highlight the background when the 'canvas' does not have focus anymore.

We can set the border color of the canvas when it has focus, with 'highlightcolor=', and specify the color constant to use.

You can set the thickness of the highlighted border by the number of pixels.

It is possible to control the 3-D edge of the canvas with the parameter 'relief'. You can see examples of the distinct reliefs you can use in the article 'Python 17 - Labels'.

The 'scrollregion' is a 'tuple' value of the four corners of the canvas. If you do not set this, or have the 'confine' set to 'false', then a scrollbar will let you scroll past the canvas area.

The parameter 'cursor' is used to set the design of the cursor when it is within the canvas area. Again, if you look at 'Python 17 - Labels', you can get a list of cursors.

'Confine' tells the program whether you can scroll past the viewable area or if the scrolling stops. This option, if true, must be used with 'scrollregion'.

Let's look at an example of setting up a canvas with a button. The button will not be on the canvas, but on the 'root' window. When a user presses the button, the focus will move from the canvas to the button, and when pressed again, the focus returns to the canvas.

Code:
from tkinter import *

root = Tk()
root.title("Canvas Highlight Color Example")
root.geometry=("500x500")

canvas = Canvas(root, width=200, height=150, bg="lightgray", highlightbackground="yellow", highlightcolor="blue", highlightthickness=5)
canvas.grid(column=0,row=0)

def push():
    focused_widget = root.focus_get()
    if focused_widget==canvas:
         button.focus_set()
    else:
        canvas.focus_set()

canvas.create_rectangle(50, 50, 150, 100, fill="purple", outline="black")
canvas.focus_set()
button = Button(root, text="Click to shift focus",command=push)
button.grid(column=0,row=2)

root.mainloop()

Here, you can see that we create a canvas on the 'root' window that has a width of 200 and a height of 150 pixels. The background color of the canvas is light gray. We set a 'highlightbackcolor' of yellow with a 'highlightcolor' of blue. The highlighted border thickness is set at 5 pixels.

The canvas is placed on the window at row 0 and column 0.

A rectangle is placed on the canvas and made purple with a black outline.

The focus is placed on the canvas. A button is then created for the 'root' window with the command 'push' to be executed when the button is pressed. The button is placed in column 0 and row 2.

When the button is pressed, the function 'push' gets control. The first thing performed in the function is to get the current widget that has focus. We place the name of the widget with focus into the variable 'focused_widget'. The program then checks if the focus is on the 'canvas', and if so, the focus is switched to the 'button'. Now, if the button has the focus, then the focus is changed to the 'canvas'.

Hopefully, you can see the basics of setting up a Canvas and using some parameters we discussed.

As I stated at the beginning, a canvas is like a painter's canvas, and you can place shapes on it. There are basically nine objects you can 'draw' or 'place' on the canvas:
  1. rectangle
  2. oval
  3. arc
  4. line
  5. polygon
  6. text
  7. image
  8. bitmap
  9. window
Rectangle

The 'rectangle' lets you specify the x and y coordinates of the top-left corner and the bottom-right corner. Keep in mind that all these objects are based on x and y pixel coordinates. The basic layout of the command is:

Code:
canvas.create_rectangle(x1, y1, x2, y2, fill="color",outline="color",width=#)

The two coordinates (x1,y1) and (x2,y2) are the two opposite corners. You can specify a fill 'color' and an 'outline' color with the outline having a specified width, as needed.

Oval

The next object is an 'oval'. It is drawn similarly to a rectangle by setting the coordinates of the top left and bottom right. Just as with the 'rectangle' parameter, you can also add 'fill', 'outline' and 'width'.

Code:
canvas.create_oval(x1, y1, x2, y2, fill="color",outline="color",width=#)

Arc

The arc is a simple curved line that is specified similarly to the 'rectangle' and 'oval', by setting the top right and bottom left corners of a box in which the arc is drawn. There are a few extra parameters, but the same ones are still used:

Code:
canvas.create_arc(x1, y1, x2, y2, fill="color",outline="color",width=#,start=#, extent=#,style='tk.style')

The 'start' parameter specifies the angle from the top left of the coordinates, where '0' is East, 90 is North, 180 is West and 270 is South. The 'extent' is the angle from the starting point, where positive values are counter-clockwise and negative values are clockwise. There are three types of styles:
  1. ARC - draws a simple arc
  2. CHORD - an arc with a straight line connecting its endpoints
  3. PIESLICE - an arc with endpoints going to a central point; this is the default if not specified
Here is an example, followed by a picture of the output in Figure 1:

Code:
from tkinter import *

root = Tk()
root.title("Arc Example")
root.geometry("900x900")

canvas = Canvas(root, width=800, height=800, bg="lightblue")
canvas.grid(row=3,column=2)
canvas.create_arc(50,50,150,150, fill="purple",outline="blue",width=3,start=0, extent=100, style=ARC)
canvas.create_arc(250, 250,350, 350, fill="purple",outline="blue",width=3,start=0, extent=100, style=CHORD)
canvas.create_arc(450, 450,550, 550, fill="purple",outline="blue",width=3,start=0, extent=100, style=PIESLICE)

root.mainloop()

Figure 1.JPG

FIGURE 1

Other parameters to change the drawing aspect are:
  • dash - specifies a dashed number of pixels that repeat ("2 3 4 5 6")
  • dashoffset - starts the dash pattern a specified distance from the start
  • stipple - use gray values (gray12, gray25, gray50, gray75)
  • state - the state of the drawn object (normal, disabled, hidden)
  • width - the width of the line in pixels; the default is 1
  • disabledfill - the color of the object if it is disabled
  • disabledwidth - width of the outline if disabled
  • disableoutline - outline color of the outline if disabled
  • disableddash - dashed pixels specified by a number if the object is disabled
  • disabledstipple - use gray values when disabled; if a fill color is set, then it will be mixed (gray12, gray25, gray50, gray75)
  • activefill - color fill when the mouse is over the object, if not disabled
  • activewidth - width of the outline when the mouse is over the object, if not disabled
  • activeoutline - color of the outline when the mouse is over the object, if not disabled
  • activedash - dashed pixels when the mouse is over the object, if not disabled
  • activestipple - gray values are used when the mouse is over the object, if not disabled (gray12, gray25, gray50, gray75)
  • tags - used to tag an object so you can perform changes on all items with the same tag
Line

You can also draw a line on the canvas. For a line, you specify the starting coordinates (x1,y1) and the ending point (x2,y2). You can also specify any of the parameters as mentioned that can be used with the line. The basic command is:

Code:
 canvas.create_line(x1, y1, x2, y2, fill='color', width=#)

The line will also use these parameters:
  • arrow - places an arrow on the specified line end (first, last, both, none), the default is none
  • arrowshape - specifies arrowhead shape as a tuple (x,y,z) where x is the distance from the end of the line to the tip of the arrow, y is the length of the angled portion from the tip to the edge of the base and c is the length from the center line to the edge of the angle. The default is (8,10,3)
  • capstyle - this is how the end of the angled portion looks when two lines join. It will be round, stop at the endpoint, or extend past the endpoint (round, butt or projecting). The default is round
  • joinstyle - how the end of the line looks without arrowheads (round, bevel, miter). Butt is the default and looks rounded, bevel appears as if it’s cut off at an angle, and miter is a sharp point
  • smooth - will make an angled line look smoother than a set of small lines joined together; the default is false
  • splinesteps - controls the smoothness; if 'smooth="true"', the default is 12
Polygon

To draw a polygon, an object with as many sides as you need, is like the others, but you specify the x and y coordinates of each corner. The last point is automatically joined to the first point to close the polygon. The polygon object also uses joinstyle, smooth, and splinesteps, as described for 'line'. An example is:

Code:
canvas.create_polygon(x1,y1,x2,y2,x3,y3...xn,xn, fill='color', outline='color')

Here, you can use the same parameters as in the line.

Text

When you want to add text to your canvas, you can use the following command:

Code:
canvas.create_text(x, y, text="abcd")

Here, you place the text 'abcd' at pixel position (x,y). There are other parameters you can use:
  • activefill - color of the text when the mouse is over it
  • activestipple - gray color pattern to use when the mouse is over the text
  • anchor - point to place the text using (x,y) as the anchor point. You can use one or more of the choices N, S, W, E
  • disabledfill - color to make the text when it is disabled
  • disabledstipple - color pattern to use when text is disabled
  • fill - color to make the text, default is black
  • font - uses system default, but you can change to any available
  • justify - justifies text (Left, Center, Right), default is Left
  • offset - stipple offset for text
  • state - the state of the text (Normal, Disabled, Hidden), default is Normal
  • stipple - gray bitmap used for the text
  • tags - used to lump multiple text objects together so they can be changed as one item
  • text - the text to display
  • width - specifies the width of a 'text box' to place text into so it might be wrapped to multiple lines
With any of these objects, you can change a parameter. When creating an object, we can assign it an ID. Such as:

Code:
line1=canvas.create_line(100, 150, 300, 150,arrow=BOTH, fill="red", width=2)

Now, we can change the object by using the 'line1' ID:

Code:
canvas.itemconfigure(line1, fill='blue', width=5)

If we placed tags on multiple items, we could change all the items with the same tag. Let's assume we have multiple items with the tag "lines":

Code:
canvas.itemconfigure("lines", fill='blue', width=5)

Image

For the 'image' object, you need to have the Python Imaging Library (PIL, now a fork called PILLOW) installed. To install PILLOW, you need to do the following in Linux (Debian based):

Code:
sudo apt update
sudo apt install python3-pip -y
sudo apt install python3-pil -y
sudo apt install python3-pil.imagetk
sudo apt install tk8.6-dev tcl8.6-dev
pip install --upgrade --no-cache-dir Pillow

For Redhat, use:

Code:
sudo yum update
sudo yum install python3-pillow
- or -
sudo dnf install python3-pillow
sudo dnf install python3-pillow-tk

Once PIL or PILLOW is installed, you will need to import it and 'tkinter' in the Python code:

Code:
import tkinter as tk
from PIL import ImageTk, Image

While setting up the canvas, you will also load an image:

Code:
image_path = r"~/path/to/your/image.png" 
pil_image = Image.open(image_path)

You can load more than PNG files; you can load JPEG, PNG, HEIC/HEIF, GIF, and SVG.

We then need to convert the image to a Tkinter image and attach it to the canvas:

Code:
tk_image = ImageTk.PhotoImage(pil_image)
canvas.image_reference = tk_image

We then need to place the image onto the canvas and specify the location:

Code:
canvas.create_image(200, 150, image=tk_image, anchor='center')

This is all an example, so let's look at actual code:

Code:
import tkinter as tk
from PIL import Image, ImageTk

root = tk.Tk()
root.title("Image Loader")
image_path = "/home/jarret/image.jpg"
pil_image = Image.open(image_path)
tk_image = ImageTk.PhotoImage(pil_image)
image_label = tk.Label(root, image=tk_image)
image_label.pack()

root.mainloop()

I could not get this to run in Visual Studio Code in Ubuntu, but it worked on Fedora. For Ubuntu, I had to run it from the command-line and it worked fine.

Bitmap

Sometimes you may wish to include an image file on the canvas. This is a 2-color XBM file.

Keep in mind that when specifying the absolute path, start with '@':

Code:
from tkinter import *
root = Tk()
canvas = Canvas(root, width=200, height=100)
canvas.pack()
canvas.create_bitmap(100, 50, bitmap="@/home/jarret/xbm.xbm", foreground="blue")

root.mainloop()

Be sure to change the path and name of the XBM file.

Window

The Window object allows you to place another object within the window. These other objects are things like a label, button, etc.

In the following example, there is a canvas with a label (l1) and a button (b1). When the button is pressed, the label is changed, and a counter (count) is used to determine how many times the button is pressed. The value is shown in the label as it is changed from its initial properties to new ones:

Code:
from tkinter import *

count=0
def clicked():
    global count
    count += 1
    l1.configure(text=f"You pressed the button {count} times.", bg="orange")

root = Tk()
root.title("Canvas create_window example")
for i in range(0,9):
    root.grid_rowconfigure(i, weight=1)
    root.grid_columnconfigure(i, weight=1)

canvas = Canvas(root, width=800, height=300, bg="lightblue")
canvas.grid()
l1 = Label(canvas, text="This is a Label", bg="yellow")
b1 = Button(canvas, text="Click Me",command=clicked)
canvas.create_window(400, 50, window=l1)
canvas.create_window(400, 120, window=b1)

root.mainloop()

As you can see, you can use regular objects within the canvas, and they work as usual as if they were not on the canvas but in a regular window.

Conclusion

I know this article went long, but I really wanted to place all the basic Canvas information in it and include the Canvas Widgets.

The next two articles will include the Canvas Methods, which will give the Canvas Widget more functionality. I definitely tried to include examples of items so the concept was easier to learn with a coded example.
 


Follow Linux.org

Staff online

Members online


Top