Chapter 15. The Canvas Widget

This chapter shows you how to use one of Tk’s most complex widgets, the canvas widget. The canvas is a general purpose widget you can use to display drawing primitives, such as arcs, lines, polygons, and other shapes; images in a variety of formats; text; and even other embedded widgets. The objects in a canvas widget can, like the text in a text box, have tags, and you can assign tagged objects their own event bindings and display attributes. With some work on your part, you can even animate the objects in a canvas. Animation and embedded widgets exceed the scope of this book, but I will show you how to use many of the canvas widget’s other features in this chapter. First, however, you get to solve a puzzle.

Got the Picture?

This chapter’s program, Got the Picture, challenges you to solve a puzzle (see puzzle.tcl in this chapter’s code directory). It starts with an image of a Byzantine gladiolus. When you click the Start button, the picture is divided into 12 rectangular tiles and mixed up on the game board. Your task is to rearrange the pieces and reconstruct the original picture. To do so, select two pieces to swap their position on the board and then click the Swap button. Figures 15.115.4 show the game at various stages.

The gladiolus is pretty to look at when you first start the game.

Figure 15.1. The gladiolus is pretty to look at when you first start the game.

Click the start button to jumble the picture.

Figure 15.2. Click the start button to jumble the picture.

Select two tiles and pick the Swap button to rearrange the puzzle pieces.

Figure 15.3. Select two tiles and pick the Swap button to rearrange the puzzle pieces.

After rebuilding the original picture, click Exit to close the window.

Figure 15.4. After rebuilding the original picture, click Exit to close the window.

The Canvas Widget

As the name itself suggests, the canvas widget creates a blank area on which you draw or paint to your heart’s programmatic content (a blank canvas, get it?). As I remarked in the introduction, the canvas widget is one of the most complex widgets in the Tk toolkit, if not the most complex. The downside of this complexity is that you have a lot to learn. The upside is that the canvas widget is immensely capable, and there is little you can’t do with it. I’ll start with a pro forma listing of the attributes and operations that are unique to the canvas widget or whose usage in the context of the canvas widget differs from what you have encountered before. Table 15.1 lists the attributes, and Table 15.2 lists the operations.

Table 15.1. Attributes for the canvas Widget

Attribute

Description

-closeenough

Specifies a floating point value that controls how close to an object the mouse cursor must be before it is “close enough” to be considered inside the item (defaults to 1.0).

-confine

If true (the default), the canvas’ view is restricted to the region defined by -scrollregion.

-height

Specifies the canvas widget’s height in any valid coordinate form.

-scrollregion

Specifies a list of coordinates of a rectangular region (left, top, right, and bottom, in that order), considered the viewable area of a canvas widget.

-state

Specifies the canvas widget’s default state, which must be one of normal, active, or hidden.

-width

Specifies the canvas widget’s width, in any valid coordinate form.

-xscrollcommand

Specifies the command sent to a scrollbar widget’s set operation when the canvas widget is scrolled horizontally.

-xscrollincrement

Specifies the increment (in screen units) in which the view moves when the view is scrolled horizontally.

-yscrollcommand

Specifies the command sent to a scrollbar widget’s set operation when the canvas widget is scrolled vertically.

-yscrollincrement

Specifies the increment (in screen units) in which the view moves when the view is scrolled vertically.

Table 15.2. Operations for the canvas Widget

Operation

$c addtag tag search ?arg ...?
$c canvasx screenx ?spacing?
$c canvasy screeny ?spacing?
$c coords id ?x y?
$c create type x y ?...?
$c dchars id first ?last?
$c delete ?id ...?
$c dtag id ?tag?
$c find cmd ?...?
$c gettags id
$c icursor id index
$c index id index
$c insert id before str
$c move id xAmt yAmt
$c postscript ?...?
$c scale id xStart yStart xScale yScale
$c select option ?id arg?
$c type id
$c xview arg ?...?
$c view arg ?...?

A dry, soulless recitation of the canvas widget’s attributes and operations won’t get you anywhere near a state of productivity with it. To get started, you’ll need to know the canvas widget’s coordinate system.

The Coordinate System

Before I proceed, let me define a convention for referring to points that I’ll use throughout the rest of this chapter. Points on canvas widgets are uniquely identified by Cartesian coordinates in the form (x,y), where x refers to a point’s X coordinate and y refers to its Y coordinate. To refer to a pair of X and Y coordinates, I’ll use the notation (x,y). So, for example, (0,0) describes the point whose X and Y coordinates are both 0; (640,480) refers to a point whose X coordinate is 640 and whose Y coordinate is 480.

With that bit of housekeeping out of way, the canvas widget is laid out in a coordinate system whose origin is the upper left-hand corner of the screen. That corner has the X and Y coordinates (0,0). X, the horizontal position, increases as you move to the right across the canvas, and Y increases as you move down the widget. The width of the canvas is set by the like named -width attribute. The height, similarly, is determined by the -height attribute. Figure 15.5 illustrates the point, so to speak.

X increases from left to right; Y increases from top to bottom.

Figure 15.5. X increases from left to right; Y increases from top to bottom.

As you can see in Figure 15.5, the upper right-hand corner has the coordinates (x,0), the lower left-hand corner has the coordinates (0,y), and the lower right-hand corner has the coordinates (x,y).

Be default, widgets are dimensioned in pixels. You can change the units by appending one of the following letters to use the corresponding unit:

  • c–Centimeters

  • i–Inches

  • m–Millimeters

  • p–Points

A “point” in this context refers to traditional printer’s points, which are X increases from left to right; Y increases from top to bottom.”. The examples I use in this chapter will be defined in terms of pixels.

The following script shows a simple canvas widget with some embedded text.

set c [canvas .c -width 320 -height 240 -bg white -relief sunken]
$c create text 100 100 -text "Hello, Tcl/Tk World!"
grid $c

The first command creates a canvas widget that is 320 pixels wide and 240 pixels tall with a white background. The entire widget has a sunken relief. The second command draws the text Hello, Tcl/Tk World! on the canvas at the coordinates (100,100). Figure 15.6 shows the resulting figure.

The canvas widget has a Spartan appearance.

Figure 15.6. The canvas widget has a Spartan appearance.

Using Canvas Objects

The canvas widget has a number of built-in drawing primitives that you can use to compose more sophisticated content. Drawing primitives describe the basic drawing operations that the widget supports. Tk’s documentation refers to drawing primitives as canvas objects, so that’s the terminology I’ll use. The list of supported canvas objects includes the following, arranged in alphabetical order:

  • arc–Arcs, or curved line segments

  • bitmaps–Bitmaps, or two-color graphic images

  • image–Images, either bitmaps or one of several full-color graphic formats

  • line–Straight lines

  • oval–Ovals, or any closed, circular shape

  • polygon–Polygons, made up of a number of connected line segments

  • rectangle–Rectangles, or any close, rectilinear shape

  • window–Embedded windows, or other widgets embedded in a canvas widget

The following sections discuss drawing each of these objects, with the exception of the window object. I omit discussion of window objects because I consider embedded windows to be an advanced topic that you probably won’t need until you progress further in your Tk scripting.

Each class of canvas objects has a number of attributes that you can use to define its behavior and appearance. Not surprisingly, some object attributes are unique to the object in question; other attributes are common to all canvas objects. To avoid considerable tedium and repetition, Table 15.3 lists these common or shared attributes.

Table 15.3. Shared Canvas Object Attributes

Attribute

Description

-activedash pattern

Defines the dash pattern of a line or outline enclosing an object in its active state.

-activefill color

Defines an object’s fill color in its active state.

-activeoutline color

Defines an object’s outline color in its active state.

-activeoutlinestipple bitmap

Defines the stipple pattern used to draw the outline of an object in its active state.

-activestipple bitmap

Defines the stipple pattern used to fill an object in its active state.

-activewidth width

Defines the width of the outline drawn around an object’s region in its active state; width can be any valid coordinate form.

-dash pattern

Defines the dash pattern of a line or outline of an object in its normal state.

-dashoffset offset

Defines the offset into an object where the dashes appear; used with -dash.

-disableddash pattern

Defines the dash pattern of a line or outline of an object in its disabled state.

-disabledfill color

Defines the fill color of an object in its disabled state.

-disabledoutline color

Defines the color of the outline when an outlined object is in its disabled state.

-disabledoutlinestipple bitmap

Defines the stipple pattern used to draw the outline of an object in its disabled state.

-disabledstipple bitmap

Defines the stipple pattern used to fill an object in its disabled state.

-disabledwidth width

Defines the width of the outline drawn around an object’s region in its disabled state; width can be any valid coordinate form.

-fill color

Defines the fill color of an object in its normal state.

-offset offset

Defines the offset of stipples in either x,y coordinates or side, where side is center or one of the standard compass points (n, ne, e, se, s, sw, w, or nw).

-outline color

Defines the color of the outline when an outlined object is in its normal state.

-outlinestipple bitmap

Defines the stipple pattern used to draw the outline of an object in its normal state.

-state state

Defines an object’s state (one of normal, active, or hidden), overriding the global state of the canvas object itself.

-stipple bitmap

Defines the stipple pattern used to fill an object in its normal state.

-tags taglist

Defines a list of one or more tags to apply to an object.

-width width

Defines the width of the outline drawn around an object’s region in its normal state; width can be any valid coordinate form.

The dash-related attributes bear on the canvas widget’s (relatively new) ability to draw lines and outlines using dashed lines. Each dash-related attribute accepts a single argument, pattern, which defines the appearance of the dashed line. To demonstrate my earlier comment about the canvas widget’s complexity, the pattern argument can take one of two forms: a list of integers whose values define the length of the line segments used to draw the dashed line, or a string that defines the proportions of the line segments used to draw the dashed line.

Drawing Arcs

An arc is a section of an oval defined by two angles: a start and an extent.

Use the following command to create arc objects:

$c create arc coords ?attr ...?

$c refers to the canvas widget on which the arc object is being drawn. The coords argument defines opposite corners of a rectangular region that would serve as the bounding box of an oval. Instead of an oval, though, an arc object consists of a curved line segment along an oval. The line segment begins at the angle specified by the -start attribute and extends through the angle specified by the -extent attribute. The attr argument specifies the attributes, if any, to apply to the resulting object. The return value of the create arc command is an identifier or handle that can be used in calls to the itemcget and itemconfigure commands and other commands that require an item identifier that refers to a canvas object.

The arc object supports the object-specific attributes listed in Table 15.4.

Table 15.4. Arc Object Attributes

Attribute

Description

-extent degrees

Specifies the length in degrees of the arc’s angular range.

-start degrees

Specifies the beginning of the arc’s angular range in degrees.

-style type

Specifies the arc style, which must be one of pieslice, chord, or arc.

An arc is drawn in the counter-clockwise direction from -start through -extent, starting at the 3 o’clock position. The -extent attribute defines the length of the arc, not its endpoint. Thus, if -start is 30 and -extent is 60, you wind up with a 90-degree arc. The degrees argument can be specified with negative values. If the -extent attribute is greater than 360 or less than -360, then the value applied will be degrees modulo 360.

The following script, arc.tcl in this chapter’s code directory, illustrates each of the possible arc styles:

set c [canvas .c -width 300 -height 300 -bg "white"]

$c create oval 10 10 290 290 -fill "#dddddd" -outline "#dddddd"

$c create arc 10 10 290 290 -start 90 -extent 60 -style arc 
    -outline "red" -activeoutline "dark red" -activewidth 5

$c create arc 10 10 290 290 -start 0 -extent 45 -style pieslice 
    -fill "light blue" -outline "light blue" 
    -activeoutline "dark blue" -activewidth 5

$c create arc 10 10 290 290 -start 180 -extent 90 -style chord 
    -fill "yellow" -outline "yellow" 
    -activeoutline "orange" -activewidth 5

grid $c

The canvas object is 300×300 pixels square with a white background. I created four canvas objects to place on the canvas: an oval and three arcs. Its purpose in this program is to show that the three arc objects are part of the same oval. I filled each arc with a different color so that you can tell them apart and recognize the difference between each of the three arc styles.

The first arc is 60 degrees long, starting from the 90-degree point on the circle and extending 60 degrees (-start 90 -extent 60). It is drawn in the (somewhat confusingly named) arc style, so it lacks an area to fill. The default color (shown when the object is in its normal state) is red (-outline red). Moving the mouse cursor over it changes its state to active, so the -activeoutline color (dark red) and the -activewidth (5 pixels) cause it to stand out.

The second arc is only 30 degrees long. Drawn in the pieslice style, it has a -fill color of sky blue. The -activeoutline and -activewidth attributes are dark blue and 5, respectively. Again, the purpose of using these attributes is to make the object stand out when the cursor hovers over it and to illustrate the behavior of the object in its active state. Notice also that the pieslice style creates an area defined by the arc itself plus two line segments between the center of the oval and the ends of the arc.

The third arc uses the chord style. Its notable feature is that the chord’s area is defined by a linear line segment between the arc’s endpoints, which are at 180 degrees and 270 degrees. Figure 15.7 shows arc.tcl’s window in a normal state, that is, with none of the embedded objects activated. Figure 15.8 shows the third arc, the yellow chord, activated.

Arcs are drawn as sections of an oval.

Figure 15.7. Arcs are drawn as sections of an oval.

Move the mouse over an object to activate it.

Figure 15.8. Move the mouse over an object to activate it.

Drawing Bitmaps

The canvas widget’s bitmap object enables you to position and display bitmaps in the canvas. However, these bitmaps are not the colored bitmaps you are accustomed to using as icons, clip art, and the like. Rather, they are a much simpler graphic. In the context of Tk, a bitmap, more properly referred to as a bitmap image, refers to an image whose pixels can display one of two colors or be transparent. Speaking strictly technically, a bitmap image consists of a foreground color, a background color, a source bitmap, and a mask bitmap. The source and mask bitmaps are a rectangular grid of zeros and ones, and both grids are the same size. In the source bitmap, pixel values of 1 map to a foreground color and pixel values of 0 map to a background color. The mask bitmap controls which portions of the source bitmap you actually see. In the mask bitmap, a value of 0 hides (masks) the corresponding pixel in the source bitmap, causing nothing to be displayed. A value of 1 allows the corresponding pixel from the source bitmap to be displayed.

This background information might be more than you wanted to know about old-fashioned bitmaps, but it is necessary to understanding why bitmap objects behave the way they do. Use the following command to create bitmap objects:

$c create bitmap x y ?attr ...?

$c refers to the canvas widget on which the bitmap object is being drawn. The x and y arguments define a point around which the bitmap will be drawn. By default, the bitmap is centered on that point, but you can use the -anchor attribute to modify where the bitmap will be positioned relative to the specified coordinates. The attr argument specifies the attributes, if any, you want applied to the bitmap object. The bitmap object supports the object-specific attributes listed in Table 15.5.

Table 15.5. Bitmap Object Attributes

Attribute

Description

-activebackground bitmap

Specifies the color of the bitmap’s 0-valued pixels in its active states. If omitted or if color empty string, nothing is displayed, resulting in a transparent effect.

-activebitmap bitmap

Specifies the bitmap to display in its active state.

-activeforeground color

Specifies the color to use for the bitmap’s 1-valued pixels in active, defaulting to black.

-anchor pos

Positions the bitmap relative its positioning point, defaulting to center.

-background color

Specifies the color of the bitmap’s 0-valued pixels in its normal state. If omitted or if color empty string, nothing is displayed, resulting in a transparent effect.

-bitmap bitmap

Specifies the bitmap to display in its normal state.

-disabledbackground bitmap

Specifies the color of the bitmap’s 0-valued pixels in its disabled state. If omitted or if color empty string, nothing is displayed, resulting in a transparent effect.

-disabledbitmap bitmap

Specifies the bitmap to display in its disabled state.

-disabledforeground color

Specifies the color to use for the bitmap’s 1-valued pixels in its disabled state, defaulting to black.

-foreground color

Specifies the color to use for the bitmap’s 1-valued pixels in its normal state, defaulting to black.

Tk’s bitmaps are primitive in comparison to other image formats and, insofar as Tk supports more modern image formats, I’m going to skip further discussion of the canvas widget’s bitmap object and direct you instead to the more capable and visually appealing image object discussed next. However, the script bitmap.tcl in this chapter’s code directory illustrates how to use the bitmap object.

Note: Still Curious about Bitmaps?

Note: Still Curious about Bitmaps?

If you want more information about Tk’s notion of bitmaps, a notion that is firmly rooted in Tk’s X Window System origins, refer to the manual page for the bitmap command (man 3tk bitmap).

Drawing Images

The image object is Tk’s general purpose object for non-bitmap images. Before you use an image object on a canvas widget, you must first create the image using the image create command. Before diving into the specifics of the canvas widget’s image object, I need to take a detour through the image command because you need to know how Tk deals with images that aren’t bitmaps.

Using the image Command

The image command is used to create and manipulate images. It can create the two-color bitmaps described earlier and non-bitmap images. By non-bitmap images, I mean image types other than the simple, two-color graphic objects I’ve already described.

Note: Why Two Commands for Working with Bitmaps?

Note: Why Two Commands for Working with Bitmaps?

The Tk core has two commands for dealing with the bitmap format due to historical reasons. The original command was bitmap. When Tk was first created, the bitmap command was added specifically to give developers native commands for working with X bitmaps, the simple two-color bitmap format you’ve already seen. As display technology evolved, Tk lagged behind. Eventually, support for full-color images was added via the image command. However, to preserve backward compatibility with older Tk code, support for the two-color bitmap format was included in the image command, and the original bitmap command was preserved. Tk continues to schlep around the old bitmap support, but most people will want to use the image command because it supports a wider variety of images.

Table 15.6 summarizes the image command’s operations.

Table 15.6. Operations for the image Command

Operation

Description

image create type

Creates an image of the specified type, returning an identifier used in other

?name? ?opts?

image operations. If specified, name is used as the identifier; otherwise, the identifier is created. Values for opts depend of the value of type.

image delete name

Deletes the image referred to by the identifier specified in name.

image height name

Returns the height in pixels of the image specified by name.

image inuse name

Returns a Boolean true if the image specified by name is being used by any widget.

image names

Returns a list of all currently defined images.

image type name

Returns the type of the image specified by name.

image types

Returns a list of all the possible image types.

image width name

Returns the width in pixels of the image specified by name.

The Tk core only supports two values for the type argument, bitmap (the two-color format described previously), and photo which, despite its name, supports several full-color image formats (PPM, PGM, and GIF), image dithering, and gamma correction. Support for many other image formats, such as JPEG, TIFF, and PNG, is available through Tk extensions. To keep things simple, I’ll use the photo image type and the GIF format.

The following script creates an image, populates it with a picture of a dahlia from my garden, and displays some information about the image (see image.tcl in this chapter’s code directory):

set img [image create photo]
$img read dahlia.gif

set f [frame .f -bg "white"]
set top [label $f.top -image $img -bg "white"]
set nfo [label $f.nfo -bg "white"]

lappend data 
    "Handle: $img
" 
    "Height: [image height $img]
" 
    "Width : [image width $img]
" 

    "In use: [image inuse $img]
" 
    "Type  : [image type $img]"
foreach datum $data {
       append t $datum
}
$nfo configure -text $t -justify left

grid $f -sticky nsew
grid $top -sticky nsew
grid $nfo -sticky w

For the purposes of the present discussion, the first two lines of code are the most important because they show how to create an image and how to populate it with image data:

set img [image create photo]
$img read dahlia.gif

The first line creates an image of type photo, storing the returned identifier in the variable $img. Notice that I did not assign an identifier to the created image using the -name attribute. As a result, the image command synthesizes a name by appending an integer to the text image. As you can see in Figure 15.9, the identifier is image1. The second line populates the image by reading the contents of the file dahlia.gif ($img read dahlia.gif).

The image command can display full-color GIFs and two-color bitmaps.

Figure 15.9. The image command can display full-color GIFs and two-color bitmaps.

To display the image, I use a label widget ($top) and set its -image attribute to $img. I used the second label, $nfo, to display some of the information about the image. Figure 15.9 shows the resulting window.

To create and populate the image in a single command, I could have written set img [image create photo -file dahlia.gif]. The attribute -file dahlia.gif tells the image create command to read the data for the image from the file dahlia.gif.

Using the image Canvas Object

The previous section just slapped an image onto a label widget for demonstration purposes. In this section, I’m going to describe how to take an image created with image create and paint it on a canvas. Use the following command to create image objects:

$c create image x y ?attr ...?

$c refers to the canvas widget on which the image object is being drawn. The x and y arguments define a point around which the image object will be drawn. By default, the image is centered on that point, but you can use the -anchor attribute to modify where the bitmap will be positioned relative to the specified coordinates. The attr arguments, if any, list the object attributes you want to apply to the resulting image object. Of the common options listed in Table 15.7, the image object supports only the -state and -tags attributes, but it also supports the object-specific attributes listed in Table 15.7.

Table 15.7. Image Object Attributes

Attribute

Description

-activeimage name

Specifies the image to display in the object’s active state, where name is the return value of an image create operation.

-anchor pos

Positions the bitmap relative to its positioning point, defaulting to center.

-disabledimage name

Specifies the image to display in the object’s disabled, where name is the return value of an image create operation.

-image name

Specifies the image to display in the object’s normal state, where name is the return value of an image create operation.

The following script, c_image.tcl in this chapter’s code directory, demonstrates how to use the image object:

set img [image create photo dahlia -file "dahlia.gif"]

set w [image width $img]
set h [image height $img]
set x [expr $w / 2]
set y [expr $h / 2]

set c [canvas .c -background "white" -height $h -width $w]
$c create image $x $y -image $img

grid $c -sticky nsew

The first command uses the image command discussed in the previous section to create an image. Unlike the earlier example, I use the -file attribute to specify the contents of the image object and also define the identifier I want assigned to the resulting image. The next two commands get the width and height of the image, storing them in the variables $w and $h, respectively. I’ll use these values to dimension the canvas widget when I define it. I also will use the width and height values to calculate the center of the image, storing these values in the variable $x and $y, respectively. I use the values to specify the anchor point of the image object when I create it.

Next, I define the canvas widget, using the width and height values just described (set c [canvas .c -background "white" -height $h -width $w]). Similarly, the following command, $c create image $x $y -image $img, creates the image object to draw on the canvas widget. Unlike the bitmap example you saw earlier, I used an image identifier rather than a filename to create the image. Both methods are acceptable; the reason I used the identifier rather than a filename is to demonstrate the proper syntax.

Figure 15.10 shows c_image.tcl’s window.

Full-color images have greater visual appeal than two-color bitmaps.

Figure 15.10. Full-color images have greater visual appeal than two-color bitmaps.

Drawing Lines

The line object is used to draw both straight and curved lines. In the simplest case, a straight line, you specify a starting and ending point. To draw curved lines, you can use either a series of joined line segments, or you can draw a spline and instruct the canvas to smooth the spline. In addition, you (or your users) can draw lines using the mouse. The example script you’ll see shortly uses the mouse to draw the lines. Other features of the canvas widget’s line object include varying the width of the line itself, the ability to place arrows at one or both ends of a line segment, and being able to specify the appearance of the points (vertexes) at which two lines meet.

Use the following command to create line objects:

$c create line coords ?attr ...?

$c refers to the canvas widget on which the line object is being drawn. The coords variable is a list of two or more pairs of (x,y) coordinates defining points along the line at which line segments are joined. The attr arguments specify the attributes you want to apply to a line segment.

The line object supports the object-specific attributes listed in Table 15.8.

Table 15.8. Line Object Attributes

Attribute

Description

-arrow where

Specifies if and how many arrowheads to draw at the ends of the line; where must be one of none, first, last, or both (defaults to none).

-arrowshape {N L W}

Specifies the arrowhead shape where (N L W) is a three-element list defining the size of the arrowhead.

-capstyle style

Specifies how to draw line caps, where style is one of butt, projecting, or round and defaults to butt; not used with -arrow or -arrowshape.

-joinstyle style

Specifies how to draw line joints, where style is one of bevel, miter, or round and defaults to miter; not used if the line only has two points.

-smooth boolean

Specifies if the line should be smoothed; used with -splinesteps.

-splinesteps number

Specifies the number of line segments used to smooth a curve; used with -smooth.

If you specify the -arrow attribute, you can use the -arrowshape {N L W} attribute to specify a three-element list that defines the arrow’s characteristics. The list elements define the lengths of arrow components, as described on the next page:

  • N–The first list element defines length of the neck, of the arrow, which is the part of the arrowhead that touches the line.

  • L–The second list element defines overall length of the arrow from its base or neck to its tip.

  • W–The third list element defines the distance from the outside edge of the line to each of the trailing points.

Figure 15.11 shows the parts of an arrow.

Who knew that drawing arrowheads on a line could be so complicated?

Figure 15.11. Who knew that drawing arrowheads on a line could be so complicated?

Tk attempts to use an internally defined heuristic to set the arrowhead size if you omit -arrowshape. In particular, if you request arrows but don’t specify -arrowshape, the neck of the arrow will be eight pixels long, the length of the arrow will be ten pixels long, and the width of the arrow will be three pixels. Thus, the equivalent -arrowshape attribute would be -arrowshape {8 10 3}.

If you want to experiment with the -arrowshape attribute, you can use the script arrow.tcl in this chapter’s code directory. It’s the script I used to create Figure 15.11.

The following script, arrows.tcl in this chapter’s code directory, shows you how to use the -arrow and -arrowshape attributes:

set c [canvas .c -width 300 -height 300 -bg "white"]

set l1 [$c create line 60 10 60 290 -arrow none]
set l2 [$c create line 120 10 120 290 -arrow first -arrowshape {8 10 3}]
set l3 [$c create line 180 10 180 290 -arrow last -arrowshape {16 20 6}]
set l4 [$c create line 240 10 240 290 -arrow both -arrowshape {40 45 13}]

grid $c -sticky nsew

After creating a canvas, I create four line objects named l1 through l4. Each line is perfectly vertical because the endpoints of each line have the same X coordinate. Similarly, they are all the same length, 280 pixels, because they share common starting and ending Y coordinates (10 and 290, respectively). The only difference between them is the shape and location of the arrows: l1 has no arrows; l2 has an arrow at its first endpoint, which is the point (120,10); l3 has an arrow at its last (or second) endpoint, which is the point (280,290); l4 has somewhat oversized arrows at both ends of the line.

Each arrowhead is bigger than the one on the line preceding it. As I mentioned earlier, the default shape corresponds to the attribute -arrowshape {8 10 3}, so I could have written the command for creating the line l2 as set l2 [$c create line 120 10 120 290 -arrow first].

Figure 15.12 shows what the lines and arrows in arrows.tcl look like.

You can control the size and shape of arrowheads.

Figure 15.12. You can control the size and shape of arrowheads.

Table 15.8 shows that the -smooth attribute accepts a Boolean argument. If you specify - smooth false (or -smooth 0), the line will not be smoothed; -smooth true or -smooth 1 causes the line to be smoothed using a Bezier curve.

Note: A Slight Oversimplification

Note: A Slight Oversimplification

Only Bezier curve smoothing is supported in the Tk core, so the -smooth attribute behaves as I described: Setting it to true results in a line that has been smoothed using a Bezier curve. However, Tk supports loading smoothing algorithms from external libraries at runtime. If this is done (a topic that is well beyond this book’s scope), then the argument to -smooth can also be the name of a smoothing algorithm to apply.

The next script, lines.tcl, creates a series of line segments and joins them using the -joinstyle attribute:

proc RandomInt {min max} {
    return [expr int($min + (rand() * ($max - $min + 1)))]
}

proc MakePoint {} {
    set x [RandomInt 0 300]
    set y [RandomInt 0 300]
    return [list $x $y]
}

for {set i 0} {$i < 6} {incr i} {
    lappend coords [MakePoint]
}
set c [canvas .c -width 300 -height 300 -bg "white"]
$c create line [join $coords] -width 5 -joinstyle round
grid $c -sticky nsew

The RandomInt procedure should be familiar to you by this point, so I’m going to skip yet another description of it. The MakePoint procedure calls RandomInt twice to create a pair of coordinates that define a single point on the canvas, returning that point as a two-element list. The for loop calls MakePoint six times to create a list of coordinates that I pass to the create line command (after exiting the for loop). The resulting “line” is actually five line segments joined, in this case, using -joinstyle round. This style of line joints, shown in Figure 15.13, gives the joints between each line segment a rounded or smooth look. Figure 15.14 uses beveled joints, which look rougher and in most cases appear to be squared off rather than forming a cleanly drawn joint. Figure 15.15 uses the default join type, -joinstyle miter, which creates sharp, fitted joints that look as if they were created by a miter saw (hence the name).

Rounded joints are smooth and soft.

Figure 15.13. Rounded joints are smooth and soft.

Beveled joints look rough and jagged.

Figure 15.14. Beveled joints look rough and jagged.

Mitered joints appear sharp and fitted.

Figure 15.15. Mitered joints appear sharp and fitted.

How do you draw a curve? Draw a segmented line, as shown in the previous example, and then apply smoothing. Specify -smooth true if you want to create lines that are smoothed instead of having pronounced angles at each joint between line segments. Increasing the number of points on the curve results in a smoother curve. To do so, specify -splinesteps num, which causes each curve to be approximated using num line segments. The next script illustrates using the -smooth and -splinesteps attributes:

proc RandomInt {min max} {
    return [expr int($min + (rand() * ($max - $min + 1)))]
}

proc MakePoint {} {
    set x [RandomInt 0 639]
    set y [RandomInt 0 479]
    return [list $x $y]
}

proc Smooth {c line num} {
    if {$num == 0} {
        $c itemconfigure $line -smooth false
    } else {
        $c itemconfigure $line -smooth true -splinesteps $num
    }
}

for {set i 0} {$i < 20} {incr i} {
    lappend coords [MakePoint]
}

set c [canvas .c -width 640 -height 480 -bg "white"]
set f [frame .f -width 640 -bg "white"]
set ln [$c create line [join $coords] -width 3]

set b0 [button $f.b0 -width 10 -text "0 Splines" 
    -command "Smooth $c $ln 0"]
set b2 [button $f.b2 -width 10 -text "2 Splines" 
    -command "Smooth $c $ln 2"]
set b3 [button $f.b3 -width 10 -text "3 Splines" 
    -command "Smooth $c $ln 3"]
set b10 [button $f.b10 -width 10 -text "10 Splines" 
    -command "Smooth $c $ln 10"]
set b50 [button $f.b50 -width 10 -text "50 Splines" 
    -command "Smooth $c $ln 50"]
set b100 [button $f.b100 -width 10 -text "100 Splines" 
    -command "Smooth $c $ln 100"]

grid $c -sticky nsew -columnspan 4
grid $f -sticky nsew -columnspan 4
grid $b2 $b3 $b10 $b50 $b100 -pady 10 -padx 10

When you initially start this script, you will see a randomly generated collection of joined line segments. There are five buttons at the bottom of the window that apply smoothing to the generated line, using two, three, ten, 50, and 100 splines. A sixth button removes all smoothing, reverting the line to its original, unsmoothed state.

The RandomInt and MakePoint procedures are the same as they were in the previous section. The Smooth procedure smoothes the line displayed in the window. It accepts three arguments: the canvas widget and line object ($c and $line, respectively) on which to operate, and an integer value, $num, that specifies how many splines to use for smoothing the line. If $num is 0, I disable smoothing completely by setting $line’s -smooth attribute to false. This causes the line to revert to its original, unsmoothed configuration. Otherwise, I set -smooth true and pass $num to the -splinesteps attribute.

As in lines.tcl earlier, I use a for loop to generate a number of X and Y coordinate values to use when creating the line. In this script, I wanted a complex line, so I generated 20 points. Next, I define a canvas widget, a frame widget to contain the smoothing buttons, and the line itself, passing the $coords list I populated in the for loop. After I create the six buttons to invoke the Smooth procedure, I display the widgets using the grid and the script is ready to go.

Figures 15.1615.21 show each smoothing option applied to the same figure.

The unsmoothed line is hideous to behold.

Figure 15.16. The unsmoothed line is hideous to behold.

Two splines don’t make much of a difference.

Figure 15.17. Two splines don’t make much of a difference.

Three splines begin to soften the line.

Figure 15.18. Three splines begin to soften the line.

Ten splines result in a nice smooth figure.

Figure 15.19. Ten splines result in a nice smooth figure.

Fifty splines doesn’t make an appreciable difference.

Figure 15.20. Fifty splines doesn’t make an appreciable difference.

One hundred splines is overkill.

Figure 15.21. One hundred splines is overkill.

As you can see from these six figures, the sweet spot for smoothing a line, or at least the line in this program, is somewhere between three and ten points.

Drawing Ovals

The oval canvas object is defined just like the arc object, by specifying two sets of coordinates that create a bounding box. If the bounding box is square, the resulting oval will be circular; otherwise, you wind up with a non-circular ovoid shape.

Use the following command to create oval objects:

$c create oval coords ?attr ...?

$c refers to the canvas widget on which the oval object is being drawn. The coords arguments consist of two (x,y) coordinates that define the oval’s bounding box. As usual, attr specifies any attributes you want applied to the object. Note that if coords defines a square bounding box, the resulting oval will be circular.

The oval object lacks object-specific attributes, but does support the common object attributes listed in Table 15.3 earlier in the chapter. Most of the time, you will want to set either the color of the oval’s interior, its outline, or both. The following script, oval.tcl in this chapter’s code directory, illustrates several ovals:

set c [canvas .c -width 240 -height 100 -bg "white"]

$c create oval 10 10 90 90 -width 5 -fill "sky blue" -outline "dark blue"
$c create oval 110 10 140 90 -width 5 -fill "chartreuse" -outline "dark green"
$c create oval 150 30 230 70 -width 5 -fill "yellow" -outline "orange"

grid $c -sticky nsew

This is a pretty simple script, drawing three ovals. The first is a sky blue circle with a dark blue outline. The second is a vertically elongated chartreuse oval with a dark outline. The third is a horizontally stretched oval with a yellow background and an orange foreground. Figure 15.22 shows the resulting window.

An oval is an oval is an oval.

Figure 15.22. An oval is an oval is an oval.

Drawing Polygons

The canvas widget’s polygon object is just what the name implies, a polygon made up of an arbitrary number of line segments. Like the line object, the resulting shape can be smoothed. Use the following command to create polygon objects:

$c create polygon coords ?attr ...?

$c refers to the canvas widget on which the polygon object is being drawn. The coords argument is a list of at least three (x,y) coordinates that define line segments, joined at their endpoints to form the vertexes of the closed shape. Also like the line object, you can specify a particular join style for the vertexes, and, if you want a curved polygon rather than a linear one, you can use the -smooth and -splinesteps attributes to apply a smoothing algorithm. In addition to the common canvas object attributes listed in Table 15.3, the polygon object supports the object-specific attributes shown in Table 15.9.

Table 15.9. Polygon Object Attributes

Attribute

Description

-joinstyle style

Specifies how to draw line joints, where style is one of bevel, miter, or round and defaults to miter; not used if the line only has two points.

-smooth method

Specifies the algorithm used to smooth the line; used with -splinesteps.

-splinesteps number

Specifies the number of line segments used to smooth a curve; used with -smooth.

The following script, polygon.tcl in this chapter’s code directory, shows you how to program the polygon object:

proc Smooth {c line num} {
    if {$num == 0} {
        $c itemconfigure $line -smooth false
    } else {
        $c itemconfigure $line -smooth true -splinesteps $num
    }
}

set c [canvas .c -width 400 -height 400 -bg "white"]
set f [frame .f -width 400 -bg "white"]

set x 80
set y 80
set coords {160 80 240 80 240 160 320 160 320 240 240 240 
    240 320 160 320 160 240 80 240 80 160 160 160}
set poly [$c create polygon $coords -width 5 
    -fill "blue violet" -outline violet]

set b0 [button $f.b0 -width 8 -text "0 Splines" 
    -command "Smooth $c $poly 0"]
set b2 [button $f.b2 -width 8 -text "2 Splines" 
    -command "Smooth $c $poly 2"]
set b10 [button $f.b10 -width 8 -text "10 Splines" 
    -command "Smooth $c $poly 10"]
set b50 [button $f.b50 -width 8 -text "50 Splines" 
    -command "Smooth $c $poly 50"]

grid $c -sticky nsew -columnspan 4
grid $f -sticky nsew -columnspan 4
grid $b0 $b2 $b10 $b50 -pady 10 -padx 5

This script is very similar to the splines.tcl script presented earlier in the chapter. The most significant difference is that instead of generating the points randomly, I specify them manually:

set coords {160 80 240 80 240 160 320 160 320 240 240 240 
    240 320 160 320 160 240 80 240 80 160 160 160}

Starting from the upper left-hand corner of the polygon I want to draw (see Figure 15.23), I define each vertex in order, moving clockwise around the figure until I get to the last point. Defining them in order ensures that the line segments are connected properly. The smoothing buttons include a different mix of smoothing options, but, other than the different canvas object and the smaller set of smoothing buttons, not much is new here. Figures 15.2315.26 show the original polygon and the results after applying 2, 10, and 50 splines for smoothing.

The original polygon is crisp and sharp.

Figure 15.23. The original polygon is crisp and sharp.

Adding two splines makes it angular, but not smooth.

Figure 15.24. Adding two splines makes it angular, but not smooth.

Even after adding 10 splines, the polygon still displays a hint of linearity.

Figure 15.25. Even after adding 10 splines, the polygon still displays a hint of linearity.

The addition of 50 splines removes all visible straight lines.

Figure 15.26. The addition of 50 splines removes all visible straight lines.

Drawing Text

The canvas widget’s text object displays a string of characters, arranged in one or more lines, on the canvas. Like the other canvas objects you have seen in this chapter, the text object supports a variety of formatting operations. In addition, unlike the other canvas objects, the text object also allows you to use text insertion and manipulation commands such as insert, index, select, icursor, and so forth.

To create a text object, use the following command:

$c create text x y ?attr ...?

$c refers to the canvas widget on which the text object is being drawn. The arguments x and y define an anchor point on which the text is centered (using the -anchor attribute described in Table 15.10). As usual, you can specify one or more attributes to further define the text object being created. The text object supports the object-specific attributes listed in Table 15.10.

Table 15.10. Text Object Attributes

Attribute

Description

-anchor pos

Positions the bitmap relative to its positioning point, defaulting to center.

-font name

Specifies the font to use for the text item; defaults to a system-dependent font.

-justify how

Specifies text justification, which must be one of left, right, or center; defaults to left.

-text string

Specifies the text to display.

-width length

Specifies the maximum line length; defaults to zero.

The anchor point defines a point on the canvas relative to which the text object is positioned. The default setting is -attribute center, meaning that the text object will be centered on the anchor point. Other values include any one of the compass points n, ne, e, se, s, sw, w, and nw (for north, northeast, east, southeast, south, southwest, west, and northwest, respectively).

The font used the platform-specific font. If you want to specify a particular font, use the -font name attribute, where name is a valid font name for the host platform. The -justify option specifies how to justify the text and is meaningful only if the text spans multiple lines.

The -text string attribute defines the initial text contents of the text object. Newline characters embedded in string create line breaks in the text. At runtime, of course, you can modify the contents of the text object using the insert and delete commands.

To define the width of the text, use the -width attribute (no surprise there). If -width is zero (the default setting), text is broken into lines at newline characters. Otherwise, the text will be broken at length. In this case, lines that exceed length are broken immediately before the space character closest to length.

The following script, plain_text.tcl, shows a simple example of the canvas widget’s text object:

proc ReadFile {f} {
    set fileId [open $f r]
    set s [read $fileId]
    close $fileId
    return $s
}

set c [canvas .c -bg "white"]
set t [$c create text 0 0 -anchor nw]
$c insert $t end [ReadFile "README"]

grid $c -sticky nsew
grid rowconfigure . 0 -weight 1
grid columnconfigure . 0 -weight 1

You’ve seen almost all of this code before. The only new elements are the two lines that create and populate the text widget:

set t [$c create text 0 0 -anchor nw]
$c insert $t end [ReadFile "README"]

The first line creates a text object, $t, with an anchor point of (0,0) and anchor position of nw. The result is a text object nailed to the upper left-hand corner of the canvas. The second line invokes the ReadFile procedure on the now-familiar README file and inserts the returned string at the “end” of the text object. Insofar as the text object is initially empty, the “end” is also the beginning or top. To display the window, I pin the canvas widget to all four sides of its parent window (grid $c -sticky nsew) and then use the rowconfigure and columnconfigure commands to set the -weight attribute to 1. The rationale is to allow the canvas widget (and its contents) to resize as the parent window resizes. Figure 15.27 shows the resulting window.

Resize the window to view the entire README file.

Figure 15.27. Resize the window to view the entire README file.

Analyzing Got the Picture?

In my opinion, puzzle.tcl is the most complicated script I’ve presented in this book. It uses language features discussed throughout the book. Obviously, I needed to use the canvas widget and other GUI elements such as buttons, frames, and images. It also contains a number of procedures, simple mathematics, list manipulation, looping, conditionals, and event bindings. In short, puzzle.tcl is a complete Tcl and Tk program.

Looking at the Code

#!/usr/bin/wish
# puzzle.tcl
# Create a puzzle out of an image

# Block 1
# Initialize the game board
proc InitGame {c img} {
    # Disable the Start button
    uplevel {$f.bstart configure -state disabled}
    # Create puzzle pieces
    set tiles [SplitImage $c $img]
    # Mix up the puzzle pieces
    JumbleImage $c $img $tiles
}

# Block 2
# Draw a 3x4 grid on the image
proc DrawGrid {c img} {
    set incrx 140
    set incry 150
    # Draw horiz lines
    for {set y 0} {$y < 600} {incr y $incry} {
$c create line 0 $y 419 $y -width 2 -fill yellow
    }
    # Draw vert lines
    for {set x 0} {$x < 420} {incr x $incrx} {
        $c create line $x 0 $x 599 -width 2 -fill yellow
    }
}

# Block 3
# Split the image into 12 tiles
proc SplitImage {c img} {
    # Create 12 tiles
    for {set i 0} {$i < 12} {incr i} {
        lappend tiles [image create photo t$i]
    }

    # Populate the tiles
    set sx 0
    set sy 0
    set ex 140
    set ey 150
    set incrx 140
    set incry 150
    for {set t 1} {$t <= 12} {incr t} {
        [lindex $tiles [expr $t - 1]] copy $img -from $sx $sy $ex $ey
        if {$t % 3 != 0} {
            set sx $ex
            set ex [expr $sx + $incrx]
        } else {
            set sx 0
            set ex $incrx
            set sy $ey
            set ey [expr $sy + $incry]
        }
    }
    return $tiles
}

# Block 4
# Generate a list of random numbers between 0 and 11
proc Randomize {} {
    set nums [list]
    while {[llength $nums] < 12} {
        set n [expr int((rand() * 12))]
        if {[lsearch -integer $nums $n] == -1} {
            lappend nums $n
        }
    }
    return $nums
}

# Block 5
# Redraw the image in its "puzzle" form
proc JumbleImage {c img parts} {
    # Clear the board
    $img blank

    # Draw the jumbled image
    set sx 0
    set sy 0
    set ex 140
    set ey 150
    set incrx 140
    set incry 150
    set nums [Randomize]
    for {set t 1} {$t <= 12} {incr t} {
        set i [lindex $nums [expr $t - 1]]
        set tile [lindex $parts $i]
        $img copy $tile -to $sx $sy $ex $ey
        if {$t % 3 != 0} {
            set sx $ex
            set ex [expr $sx + $incrx]
        } else {
            set sx 0
            set ex $incrx
            set sy $ey
            set ey [expr $sy + $incry]
        }
    }
    # Pass the (x,y) coordinates under the cursor to SelectTile
    bind $c <Button-1> [list SelectTile %x %y]
}

# Block 6
# When a tile is selected, add it to the list of selected tiles
proc SelectTile {x y} {
    global selected

    # Map a the (x,y) coordinates to a tile
    if {$x >= 0 && $x < 140 && $y >= 0 && $y < 150} {
        lappend selected 0 0 140 150
    }
    if { $x >= 140 && $x < 280 && $y >= 0 && $y < 150} {
        lappend selected 140 0 280 150
    }
    if {$x >= 280 && $x < 420 && $y >= 0 && $y < 150} {
        lappend selected 280 0 420 150
    }
    if {$x >= 0 && $x < 140 && $y >= 150 && $y < 300} {
        lappend selected 0 150 140 300
    }
    if {$x >= 140 && $x < 280 && $y >= 150 && $y < 300} {
        lappend selected 140 150 280 300
    }
    if {$x >= 280 && $x < 420 && $y >= 150 && $y < 300} {
        lappend selected 280 150 420 300
    }
    if {$x >= 0 && $x < 140 && $y >= 300 && $y < 450} {
        lappend selected 0 300 140 450
    }
    if {$x >= 140 && $x < 280 && $y >= 300 && $y < 450} {
        lappend selected 140 300 280 450
    }
    if {$x >= 280 && $x < 420 && $y >= 300 && $y < 450} {
        lappend selected 280 300 420 450
    }
    if {$x >= 0 && $x < 140 && $y >= 450 && $y < 600} {
        lappend selected 0 450 140 600
    }
    if {$x >= 140 && $x < 280 && $y >= 450 && $y < 600} {
        lappend selected 140 450 280 600
    }
    if {$x >= 280 && $x < 420 && $y >= 450 && $y < 600} {
        lappend selected 280 450 420 600
    }
}

# Swap the tiles
proc SwapTiles {c img} {
    global selected

    image create photo src
    image create photo temp
    set x1 [lindex $selected 0]
    set y1 [lindex $selected 1]
    set x2 [lindex $selected 2]
    set y2 [lindex $selected 3]
    set x3 [lindex $selected 4]
    set y3 [lindex $selected 5]
    set x4 [lindex $selected 6]
    set y4 [lindex $selected 7]

    # Get the image from the first tile
    src copy $img -from $x1 $y1 $x2 $y2

    # Get the image from the second tile
    temp copy $img -from $x3 $y3 $x4 $y4

    # Put the first image in the second one's grid
    $img copy src -to $x3 $y3 $x4 $y4

    # Put the second image in the first one's grid
    $img copy temp -to $x1 $y1 $x2 $y2

    # Clear the list variable
    unset selected
}

# Block 7
# Layout the UI
# Create the image
set img [image create photo -file "gladiola.gif"]
set w [image width $img]
set h [image height $img]

# Canvas to display the puzzle
set c [canvas .c -width $w -height $h]

# Image object to contain the puzzle
set puzzle [$c create image 0 0 -image $img -anchor nw]
DrawGrid $c $img

# Buttons to control the game
set f [frame .fbuttons -width $w -height 100]
set bStart [button $f.bstart -text "Start" 
    -command [list InitGame $c $img]]
set selected {}
set bSwap [button $f.bswap -text "Swap" -command [list SwapTiles $c $img]]
set bExit [button $f.bexit -text "Exit" -command exit]

# Display the widgets
grid $c -sticky nsew -pady {10 0} -padx 10
grid $f -sticky nsew
grid $bStart $bSwap $bExit -pady 10 -padx 5

Understanding the Code

The InitGame procedure in Block 1 is invoked when the player clicks the Start button. It initializes the game board after it has been created. InitGame takes two arguments: the canvas widget on which to draw ($c) and the image object to use. I disable the Start button to prevent accidentally redrawing the board in mid-game. Next, I call the SplitImage procedure, using the $c and $img arguments passed in to InitGame. This procedure splits the displayed image into 12 equally-sized tiles that serve as the puzzle pieces. Next, I call the JumbleImage procedure to clear the starting board and redraw the image in its mixed up form. The DrawGrid block 2 procedure just draws a series of yellow horizontal and vertical lines on the canvas. I do this mostly as a visual aid to the player.

Block 3 defines the SplitImage procedure, which takes the same canvas and image arguments described earlier. First, I create 12 empty image items and append them to the list variable $tiles. Next, I iterate over the image, moving from the upper left-hand corner to the lower right-hand corner. In each iteration of the for loop, I copy a 140-pixel by 150-pixel region of the “parent” or main image into one of the tiles. The variable declarations and the if-else statement exist for what I refer to as bookkeeping purposes. They help me keep track of the part of the image that I need to copy. After creating the tiles, I return the completed list to the calling procedure.

The Randomize procedure (Block 4) is a helper function. Its purpose is to generate a randomly ordered list of numbers between 0 and 11, inclusive. I use this random ordering to redraw the image in its puzzle, or mixed up form.

In Block 5, I define the JumbleImage procedure. In addition to the standard canvas and image arguments ($c and $img, respectively), JumbleImage accepts a third argument, parts, which is the list of tiles created by the SplitImage procedure I defined earlier. JumbleImage uses the same bookkeeping logic as SplitImage to maintain the X- and Y-coordinate values. There are some key differences, though. The code in question is reproduced below:

set nums [Randomize]
for {set t 1} {$t <= 12} {incr t} {
    set i [lindex $nums [expr $t - 1]]
    set tile [lindex $parts $i]
    $img copy $tile -to $sx $sy $ex $ey

First, I call Randomize to generate a randomly ordered list of numbers between zero and 11, inclusive. After I initialize the for loop, I extract one of the random numbers from the list. Then I use the number to index into the list of tiles and retrieve the corresponding image from the $tiles array. Then I copy that image to the specified region of the canvas object. In this way, I rearrange the picture to create the puzzle the player has to solve. Finally, I bind a single click of mouse button 1 (the left mouse button) to the procedure SelectTile, passing the object ID of the tile that was clicked.

The two procedures in Block 6 handle the mouse event and arrange for the selected images to be swapped. SelectTile takes the name of a tile as its sole argument and appends that name, which is actually the tile’s object ID, and appends it to the global list variable $selected. The SwapTiles procedure, in turn, swaps the images displayed in the two tiles.

The balance of the code (Block 7) defines and displays the game board. I create the source image (rather, the source image object) first and then use its width and height to define the canvas widget’s width and height. Next, I anchor the canvas widget to the parent window’s upper left-hand corner and then call DrawGrid to superimpose the grid lines over the image.

I use a frame widget as a container for the various button widgets that control the game. The Start button invokes the InitGame procedure described earlier. Similarly, I wire the SwapTiles procedure to the Swap button. No application is complete without an Exit button, either, so I provide that as a convenience for the player. Finally, I display the widgets, and the game is ready to play.

Modifying the Code

Here are some exercises you can try to practice what you learned in this chapter:

  • 15.1 Modify the binding for mouse button 1 to give the player visual feedback that the selected tile is, in fact, selected.

  • 15.2 Modify the code in Block 6 to prevent the user from selecting more than two tiles.

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

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