The Tk Canvas Widget

by Derek Fountain

The canvas widget in the Tk graphical user interface toolkit is a free software tool used to present graphical data. Like the Tk text widget, which I discussed in my previous article, the canvas widget is accessible from most modern scripting languages, including Tcl, Perl and Python. It provides those languages with a best of breed facility for structured graphics work.

A canvas widget can be thought of as a blank piece of paper upon which lines, shapes, text, images and other Tk widgets can be drawn. These items, once drawn, can be reconfigured in order to change their positions, colour, size, format or contents. They also can be given abilities to respond to input from the user or to react to changes when data is manipulated elsewhere in the script.

The canvas widget isn't a solution to any particular problem; rather it is an extremely flexible tool that allows developers to build solutions quickly and easily for all sorts of problems. This article describes some of the facilities canvas widget provides and suggests some uses for them.

Items, The Building Blocks

When using the canvas widget, the basic idea is to draw what are known as items. An item can be a line, an image, some text, one of a number of geometric shapes and so on. See the table below for a description of the item types that can be drawn and manipulated. Figure 1 shows a screenshot of a canvas widget displaying some of these items.

Figure 1. A Canvas Widget Displaying Some of the Supported Item Types

Range of Items the Canvas Widget Can Manipulate

Item TypeDescription
ArcAn arc shaped region, empty or filled
BitmapA simple, two colour image as is often used for an icon
ImageA full colour image such as a JPEG image
LineA line or sequence of lines, straight or bezier smoothed
OvalAn oval or circle, empty or filled
PolygonA multi-sided, irregular shaped region, empty or filled
RectangleA rectangle or square, empty or filled
TextSome text, either static or editable
WindowA Tk widget or set of widgets
OtherA user defined item type which must be coded in C

Exactly how an item is drawn depends on the options with which it is configured. Many dozens of options are available, and this article discusses and demonstrates some of them. See the canvas widget man page for a comprehensive list of all the options available.

IDs, Tags and Binding

When an item is drawn on a canvas it becomes an independent entity. Each individual item is given a unique ID that the developer can use to reconfigure the item's options. When any of an item's options are changed, the canvas widget ensures the item is redrawn with its new configuration. Not only does this redrawing happen immediately, it also is quick enough to support animations and direct mouse control of hundreds or possibly thousands of items.

As well as their unique Tk-assigned IDs, items also can be tagged with one or more names chosen by the developer. Tags work in the same way as those offered by the Tk text widget. As well as providing an easier way to reference a single item (a sensible name instead of a number), this tagging mechanism also allows items to be grouped together logically. All items given the same tag can be treated as one single item.

Once an item has been given one or more tags, the canvas widget allows pieces of code to be bound to those tags. This is the feature of the canvas widget that enables dynamic behaviour. I covered the tag and bind approach in some detail in my article on the Tk text widget. Because the principles and implementation are virtually identical for the canvas widget, I am not going to repeat the information here. A simple example of tag and bind applied to a canvas item should suffice:



.canvas create line 0 0 100 100 -tag diagonal_line
.canvas bind diagonal_line <Double-Button-1> {
    puts "Leave that alone!"
}


This creates a line item on the canvas from point 0,0 to point 100,100 and tags it with the name diagonal_line. If the user double-clicks mouse button one on the line (or any other item with the tag diagonal_line) a message is printed. Most uses of the canvas widget use this tag and bind approach extensively

Developing Applications with the Tk Canvas Widget

Given a problem that requires a graphical solution, the Tk canvas widget is often the first tool experienced script writers use. Complex problems often can be solved using a canvas widget and a few dozen lines of script. Figure 2 shows a screenshot of a script that uses a canvas to present an interactive editor for the shapes of the arrowheads that can terminate line items. This program is part of the demonstration suite that comes with Tcl/Tk; it takes about 200 lines of code.

Figure 2. The canvas widget enables useful utilities to be written quickly and easily.

Useful as the canvas widget is for small applications and tools, it also has the flexibility and scalability to work well as a solution for enterprise level problems. Let's consider, using an example, how a canvas widget can be used to solve a requirement I once came across while working in the oil industry.

An Example Script

Consider an oil refining installation, where oil is pumped through a series of machines under the control of a number of valves. As a software developer, you might have written the code that drives the machines and their controlling valves. Now, you'd like to write a graphical front end where the operator can click on a valve or machine and have it open or close, start or stop.

I have written a script to demonstrate how the Tk canvas widget can be used to implement a solution to such a scenario. Use the link below to access the script. Figure 3 shows a screenshot.

Figure 3. The canvas widget can be used to mimic and help control real world objects, installations and equipment.

My simple, fictitious oil installation uses four machines: a pump, a filter, a refiner and a machine for taking oil samples. These all are controlled by a set of valves, any one of which can be open, closed or in the process of moving. A temperature sensor can be positioned over any point of the installation.

The demonstration script enables the graphical representations of the machines and valves to respond to mouse clicks and the temperature sensor to be moved around. In a real-world scenario, integrating the machine and valve controlling code with a front end in this way would be a fairly straightforward task.

Let's have a closer look at how the canvas widget is used to represent and control this hardware.

The Machines

The machines are drawn on the canvas as a simple rectangle with the name as a text string inside it. The rectangle is filled with a colour to indicate the machine's status.



.canvas create rect [list $x $y $w $h] \
    -tag [list machine $name] -width 3 -fill green
.canvas create text [expr $x + $w/2] \
                    [expr $y + $h/2] -text $text \
    -tag [list machine $name] -anchor c -font $font]


The text is placed in the calculated centre of the rectangle. Note how both items are given two tags, machine and the actual machine name itself. The machine tag puts the items in a group that includes all machines. Doing so ensures that a mouse click on any item that is part of any machine is caught and handled by the correct piece of code. This is the binding:



.canvas bind machine <Button-1> {
    operateMachine %W [%W canvasx %x] [%W canvasy %y]
}


In a real situation, the operateMachine procedure would call the code that drives the machine hardware. In my example I simply change the status of the machine in the script, which in turn changes the colour of the machine in the display.

The Valves

The valves are represented on screen by an icon made of four triangles. The colour of these triangles shows whether the valve is open, closed or moving. I draw a valve as four filled polygon items, each one triangle shaped. These four polygons are given a shared tag so I can control a valve icon with one command when necessary. Each polygon also is given a unique tag so I can control the colour of each individual triangle.

Each valve icon is wrapped in an invisible box sized and placed so it encloses the four triangle items. The purpose of this item is to catch mouse clicks. Without it, the user might try to click on the valve icon but actually hit one of the gaps between the triangles, in which case the mouse click would be ignored. Using an invisible wrapper item over the top of the displayed items is a common technique for ensuring all mouse clicks are caught.

The canvas widget has a useful subcommand that returns the coordinates of a box that just encloses the specified item or tagged items. For example, to get the bounding box that encloses the four triangles included in the instream valve:



set boxCoords [.canvas bbox instreamValve]


Given the information in boxCoords, an unfilled polygon item with line width of zero pixels can be placed over the valve icon. It's this polygon item that the valve handling code is bound to.

Temperature Sensor

The temperature sensor is drawn as a small red rectangle item at the intersection of two dashed lines:



.canvas create line $x $top $x $bottom -width 2 \
    -tag [list sensor_v sensor_line sensor] -dash { 2 2 }
.canvas create line $left $y $right $y -width 2 \
    -tag [list sensor_h sensor_line sensor] -dash { 2 2 }
.canvas create rect [expr $x-3] [expr $y-3] \
                    [expr $x+3] [expr $y+3] \
    -tag [list sensor_box sensor] -width 1 -fill red


The dash pattern a line item can be drawn with is configurable. In this case I've used a pattern of two pixels on and two pixels off. Regularly updating a dash pattern is a simple way to animate the marching ants effect found on many selection mechanisms.

The canvas contains a binding for dragging (that is, a mouse motion with button 1 pressed) these items. The sensor can be moved by dragging it directly or by dragging the lines that control it:



.canvas bind sensor <B1-Motion> {
    operateSensor %W move [%W canvasx %x] [%W canvasy %y]
}


This binding applies to all items in the sensor graphic, both lines and the centre rectangle. In order to find out which item is being dragged, the operateSensor code asks the canvas which item is currently under the mouse pointer, using the special current tag:



set draggedItem [.canvas find withtag current]


In a real application, the operateSensor code would call the hardware that repositions the sensor. The sensor also would be polled on occasion, whereas in my demonstration script a random number is used for temperature display purposes.

Tcl Traces

My demonstration script takes advantage of Tcl's ability to attach a procedure to variable accesses. This tracing often is used when a canvas widget is employed to maintain a graphical representation of a data set in a program. Tracing can be used to ensure that whenever data is changed, the routine to update the canvas is called automatically. A simple example from my demonstration script is the way I keep the sensor status text up to date. A trace is set in place:



trace add variable sensorState write setSensorText


It ensures that whenever the variable sensorState is written to, the procedure setSensorText is called automatically. This procedure updates the text displayed on the canvas widget. Other languages have features that can be used in similar ways. Perl/Tk users might want to look at Perl's tie mechanism, for example.

Other Interesting Canvas Features

The canvas widget has a few more features in its toolbox that occasionally prove to be huge time savers. For instance, all items on a canvas that have been drawn using vectors (that is, items with specified coordinates such as polygons and rectangles) can be scaled (resized) dynamically. A single command can be issued to the canvas widget to request that it rescale any or all such items. For example, this command rescales all the items on the canvas by 1.1:



.canvas scale all 0 0 1.1 1.1


In other words, this single line of code is a +10% zoom facility.

The canvas widget also has the built-in ability to generate a postscript representation of its current display. Many options are available to control the postscript output. The simplest way of getting a postscript dump of a canvas display is to call the postscript generation procedure with its default parameters from a specified keypress:



bind .canvas <KeyPress-p> {
    .canvas postscript -file /tmp/canvas.ps
}


Conclusion

The Tk canvas widget largely has succeeded in hitting the perfect balance point between functionality and usability. It provides pretty much everything similar widgets from other toolkits do, plus a whole lot more in most cases. Yet, partnered with any one of a number of scripting languages, it is simple to use and incredibly efficient in terms of developer effort and lines of code needed to drive it.

Given its ability to provide solutions to a wide range of problems, the Tk canvas widget is a tool with which all application software developers should be familiar.

Load Disqus comments

Firstwave Cloud