Chapter 10. Button Widgets

Unless you’ve been living in an unelectrified cave for the last decade, you are accustomed to clicking buttons. After providing more information about the first of Tk’s three geometry managers, pack, this chapter looks at Tk’s button widgets. In addition to learning how to use buttons, I’ll show you how to use color in a Tk application and how to bind buttons to commands and events.

Memory Test

This chapter’s game, Memory Test, flashes buttons arranged in a grid in a given sequence (see Figures 10.1-10.4) and then asks you to repeat that sequence from memory, much like the game Simon (except there are no musical tones to accompany the flashing buttons).

Click the Play button to begin the game.

Figure 10.1. Click the Play button to begin the game.

Remember the order in which the colored buttons flash.

Figure 10.2. Remember the order in which the colored buttons flash.

Click the buttons in the correct sequence to win.

Figure 10.3. Click the buttons in the correct sequence to win.

Click the buttons out of order, and you’ll lose.

Figure 10.4. Click the buttons out of order, and you’ll lose.

Packed and Ready: The pack Geometry Manager

As I remarked in Chapter 9, geometry managers arrange widgets on the screen, register them with the underlying windowing system, and manage their display on the screen. Of the three geometry managers Tk uses, pack, grid, and place, pack and grid are general-purpose. I start with pack because it is the one I use most often and because it was the first geometry manager Tk used. I’ll discuss the grid geometry manager in Chapter 11, “Windows, Frames, and Messages.”

The first thing to understand is that pack is constraint-based, meaning that rather than specifying precise placement information for widgets, you tell pack in general terms how you want widgets placed, that is, you define limits or constraints on widget placement, and then allow pack to work out the placement details itself. The upside to this method is that pack is easier to use precisely because you don’t have to fuss with placement details; the downside to this placement model is that if you don’t understand how pack’s algorithm works, the results you get will surprise you or might not be what you wanted or expected.

Note: Masters, Slaves, Parents, and Children

Note: Masters, Slaves, Parents, and Children

Recall that widgets are arranged hierarchically. As I use them in this book, the terms master and parent are equivalent and refer to widgets that contain other widgets. Likewise, slave and child are equivalent and refer to widgets that are contained within master or parent widgets.

The packer works from an ordered list of slaves referred to as the packing list. When you use -in, -before, and -after, you are inserting new slaves into specific positions in the packing list. Otherwise, as widgets are packed, they are added to the end or bottom of the packing list. After creating a cavity into which to place widgets, the packer processes the packing list in order, doing the following:

  1. Assigns a rectangular area, or parcel, of the cavity to the widget on the side of the cavity specified by -side (the bottom if -side is not specified).

  2. Dimensions the slave, which is its requested width plus twice the sum of -ipadx and -padx and its requested height plus twice the sum of -ipady and -pady.

  3. If -fill is x or both, the width is expanded to fill the parcel width minus twice the value -padx, if specified. Likewise, if -fill is y or both, the height is expanded to fill the parcel height minus twice the value of -pady, if specified. If -fill is all, both the width and height are expanded to fill the parcel.

  4. If the widget is smaller than the parcel, the value of -anchor, if specified, controls where the widget will be placed, offset by the value(s) of -padx and/or -pady, if specified.

  5. The size of the parcel is subtracted from the cavity, leaving the remaining cavity space for the next slave.

  6. If the size of the parcel isn’t large enough to contain the slave, the slave gets the remaining space.

  7. If the cavity space shrinks to zero, remaining slaves will be unmapped (removed from the screen) until the master window is large enough to hold them.

  8. After all slaves have been placed, remaining cavity space, if any, is evenly allocated to all slaves for which the -expand option was set. Horizontal space is evenly allocated to expandable slaves whose -side option, if any, specified left or right; and vertical space is evenly allocated to expandable slaves whose -side option, if any, specified top or bottom.

Figure 10.5 illustrates the layout of a widget on the screen.

Widget layout.

Figure 10.5. Widget layout.

To get you over the initial hump, here are some tips for using pack:

  • The parent must be created before you can pack widgets into it.

  • Pack vertically or horizontally, but not both.

  • Use -in, -before, and -after to specify a slave’s master widget.

Table 10.1 shows the arguments that pack supports.

Table 10.1. pack Arguments

Option

Description

- after other

Inserts the slave window in the packing order after the window specified by other and uses other’s master as the slave’s master.

-anchor position

Places each slave window at position in its parcel (defaults to center).,

-before other

Inserts the slave window in the packing order before the window specified by other and uses other’s master as the slave’s master.

-expand Boolean

Expands the slave to consume extra space in the master if true; defaults to 0 or false.

-fill style

Defines the fill behavior of a slave that is smaller than its parcel; must be one of none, x, y, or both.

-in other

Inserts the slave window into the packing order of the master specified by other.

-ipadx n

Specifies n as the amount of internal horizontal padding of the slave.

-ipady n

Specifies n as the amount of internal vertical padding of the slave.

-padx n

Specifies n as the amount of horizontal padding to add outside the widget.

-pady n

Specifies n as the amount of vertical padding to add outside the slave.

-side side

Packs the slave against side of the widget; must be one of left, right, top, or bottom (defaults to bottom).

configure

Sets the configuration options for slave widgets.

forget

Removes specified slaves from the packing list, removing them from the screen.

info

Returns the configuration of the specified slave.

propagate

Disables/enables propagation of a master’s geometry settings to its slaves.

slaves

Returns a list of slaves in a specified master’s packing order.

Button, Button, Who’s Got the Button?

Tk’s buttons come in the three flavors listed below:

  • button—. Creates button widgets.

  • checkbutton—. Creates checkbutton widgets.

  • radiobutton—. Creates radiobutton widgets.

Actually, there’s a fourth button widget, the menubutton, but I cover it in the section, “A Smörgåsbord of Menus,” because it makes more sense to discuss menubuttons in the context of the menu widget. Figure 10.6 shows what each of these buttons looks like in its default, unconfigured state.

Basic Tk button widgets.

Figure 10.6. Basic Tk button widgets.

Sure, they’re not much to look at just now, but as you’ll see in the rest of this section, Tk’s button widgets are highly configurable and support most, if not all, of the options and capabilities sophisticated GUI users have come to expect.

Plain Vanilla Buttons

I’ll start with the simplest of Tk’s buttons, the eponymously named button widget. Its syntax is:

button name ?args?

This command creates a button whose name is name with the attributes and command options specified by opts. Table 10.2 lists the arguments the button command supports.

Table 10.2. button Arguments

Option

Description

-command

Specifies the command to execute when the button is pressed.

-default

Defines the state used for the default appearance, which must be one of normal, active, or disabled.

-height

Sets the button’s height.

-overrelief

Specifies a non-default relief when the cursor hovers over the button.

-state

Specifies the button’s current state, which must be one of normal, active, or disabled.

-width

Sets the button’s width.

cget

Returns the value of the specified option.

configure

Returns or sets the button’s configuration options.

flash

Flashes the button by alternating between its active and normal colors.

invoke

Executes the Tcl command associated with the button, if one is defined.

Tk’s widget commands support two kinds of arguments: options and commands. Options are prefixed with a hyphen, and each option is usually followed by an argument. The -command option, for example, specifies a Tcl command to execute when the widget is executed, while the -height option specifies the widget’s height. Commands perform operations on the widget, such as the configure command, which modifies or queries the widget’s current configuration, and the flash command, which causes the widget to flash. Some commands accept no arguments, others accept a single argument, and still others accept multiple arguments.

In the case of the button command, you’ll usually want to modify the width or height, the command it executes, or click it programmatically by using the invoke command.

The following script, button.tcl in this chapter’s code directory, gets you started with buttons:

proc FlashButton {b} {
    # Save the original color
    set ocolor [$b cget -activebackground]    $b configure -activebackground red
$b flash    # Restore the original color
    $b configure -activebackground $ocolor
}

set lFlash [label .flash -width 20 -text "Flash Activate button"]
set lActivate [label .activate -width 20 -text "Activate Exit button"]

set bExit [button .e -width 8 -text "Exit" -state disabled 
    -command exit]
set bActivate [button .a -width 8 -text "Activate" 
    -command {$bExit configure -state normal}]
set bFlash [button .f -width 8 -text "Flash" 
    -command {FlashButton $bActivate}]

pack $lFlash
pack $bFlash -pady {0 20}
pack $lActivate
pack $bActivate -pady {0 20}
pack $bExit

When you execute this script, the initial screen should resemble Figure 10.7.

Basic Tk command buttons.

Figure 10.7. Basic Tk command buttons.

The FlashButton procedure accepts a single argument, the name of a button to flash (using the button widget’s flash command). Because the flash is so quick, I use the configure command to change the active background color to red (-activebackground red) before flashing the button. That way, when the button flashes, it is easier to see. After flashing the button, FlashButton restores the original background color, which is stored in the ocolor variable using the cget command at the beginning of the procedure.

Next, I create two labels to provide some descriptive text for their associated buttons. As a convention, I use an initial lowercase l (ell) to indicate that the variables (lFlash and lActivate) refer to label widgets. As with most coding conventions, it matters less what the convention is than it does that you choose and use one. This is mostly for your own sanity, but it helps other people who read your code to understand it, too. Similarly, for readability, I assigned easily recognized pathnames to the label widgets, .flash for the Flash button’s label and .activate for the Activate button’s label.

The next step is to create the buttons (I use an initial b for variables that refer to button widgets). The Exit button, a reference to what is stored in the bExit variable, is initially disabled (-state disabled), as you can see in Figure 10.7. When enabled and clicked, it executes the Tcl exit command to terminate the script.

The Activate button (accessed through the bActivate variable) comes next. When clicked, it changes the state of the Exit button to normal, meaning that the Exit button is enabled. I defined the Activate button after the Exit button because I needed to refer to $bExit when specifying $bActivate’s -command option. Similarly, the Flash button ($bFlash) had to be defined after $bActivate because $bFlash’s -command invokes the FlashButton procedure with an argument of $bActivate.

Click the Flash button to flash the Activate button. When you’re done amusing yourself with that, enable the Exit by clicking the Activate button. Finally, click the newly enabled Exit button, shown in Figure 10.8, to terminate the script.

One Tk button can modify another button’s state.

Figure 10.8. One Tk button can modify another button’s state.

Okay, so it isn’t a mind-numbingly awesome program, but it does demonstrate some of the key features of Tk’s button widget.

Check Buttons

Tk’s checkbutton widget is equivalent to what are known as option buttons in other windowing toolkits. Its syntax is identical to the button widget’s syntax:

checkbutton name ?args?

In addition to all the functionality of plain vanilla Tk buttons, checkbuttons also have a variable associated with them. When you select a checkbutton, the indicator is set (or “on”), and its linked variable’s value is set to 1 (the default) or the value specified by the -onvalue attribute. Deselecting or clearing a checkbutton clears or unsets the indicator and sets the variable’s value to 0 by default or the value specified by the -offvalue attribute. You can also use a checkbutton widget’s -command option to execute a Tcl command when the widget is clicked with the left mouse button.

Check buttons are often used in windows or tabs that set multiple options for program behavior. Unlike radio buttons, discussed in the next section, multiple checkbutton widgets can be set at once (radio buttons are linked together and only one can be set at a time). When used in this way, script code interrogates the value of the associated variable and sets the program’s behavior based on that value.

Tk’s checkbutton widget supports a wider array of options than does the plain button widget, as you can see in Table 10.3.

Table 10.3. checkbutton Arguments

Options

Description

-command

Specifies the command to execute when the checkbutton is pressed.

-height

Sets the checkbutton’s height.

-indicatoron

Determines if the indicator is on (true) or off (false).

-offrelief

Specifies the relief if the indicator is not drawn and the checkbutton is off.

-offvalue

Sets the value of the associated variable when the checkbutton is off (defaults to 0).

-onvalue

Sets the value of the associated variable when the checkbutton is on (defaults to 1).

-overrelief

Specifies a non-default relief when the cursor hovers over the checkbutton.

-selectcolor

Specifies the checkbutton’s background color when it is selected.

-selectimage

Specifies the image to display when the checkbutton is selected and -image is also specified.

-state

Specifies the checkbutton’s current state, which must be one of normal, active, or disabled.

-variable

Sets the name of the variable associated with the checkbutton.

-width

Sets the checkbutton’s width.

cget

Returns the value of the specified option.

configure

Returns or sets the checkbutton’s configuration options.

deselect

Unselects the checkbutton and sets its associated variable to the value specified by the -offvalue attribute.

flash

Flashes the checkbutton by alternating between its active and normal colors.

invoke

Executes the Tcl command associated with the checkbutton, if one is defined.

select

Selects the checkbutton and sets its associated variable to the value specified by the -onvalue attribute.

toggle

Toggles the checkbutton’s selected state and sets the value of the associated variable to its “on” or “off” value as appropriate.

The following listing, ckbutton.tcl in this chapter’s code directory, shows how you can use checkbuttons and regular buttons together:

proc DoButton {action ckbuttons} {
    foreach ckbutton $ckbuttons {
        switch $action 
            toggle { $ckbutton toggle } 
            clear { $ckbutton deselect } 
            set { $ckbutton select }    }
}

proc ShowStatus {ckbuttons} {
    foreach ckbutton $ckbuttons {
        set var [$ckbutton cget -variable]      global $var      set val
[set $var]        puts "$var = $val"    }
}

set ckTop [checkbutton .cktop -text "Top checkbutton" 
    -command {ShowStatus .cktop}]
set ckMid [checkbutton .ckmid -text "Middle checkbutton" 
    -command {ShowStatus .ckmid}]
set ckBot [checkbutton .ckbot -text "Bottom checkbutton" 
    -offvalue "OFF" -onvalue "ON" -command {ShowStatus .ckbot}]

set ckbuttons [list $ckTop $ckMid $ckBot]

set bExit [button .eb -width 8 -text "Exit" 
    -command exit]
set bToggle [button .toggle -width 8 -text "Toggle All" 
    -command {DoButton toggle $ckbuttons}]
set bClear [button .clear -width 8 -text "Clear All" 
    -command {DoButton clear $ckbuttons}]
set bSet [button .set -width 8 -text "Set All" 
    -command {DoButton set $ckbuttons}]
set bShow [button .show -width 8 -text "Show All" 
    -command {ShowStatus $ckbuttons}]

pack $ckTop $ckMid $ckBot -anchor w
pack $bToggle -pady 5 -padx 5 -side
left pack $bClear $bSet $bShow $bExt -pady 5 -padx {0 5} -side left

The first procedure, DoButton, takes two arguments: an action to perform and a list of checkbuttons on which to perform the requested action. action corresponds to one of three command buttons: toggle (for the Toggle All button), clear (for the Clear All button), and set (for the Set All button). Toggling the checkbuttons means reversing their selected status; clearing them means calling each checkbutton’s deselect command; setting them means invoking their select command. I use a foreach loop to iterate through each button and perform the requested operation.

The second procedure, ShowStatus, displays the value of each checkbutton’s associated variable. It accepts a single argument, a list of checkbuttons over which to iterate. Again, using a foreach loop, I iterate over each checkbutton, performing the following steps:

  1. Retrieve the name of the widget’s associated variable.

  2. Declare that variable name as a global variable so I can access its value.

  3. Retrieve the value of the widget’s associated variable.

  4. Display both the variable’s name and its value.

Step 3, fetching the value of the widget’s linked variable, is a bit of a trick. Recall that the set command called with just a variable name (for example, set $var) returns the value of that variable. So the code set val [set $var] evaluates the nested command [set $var] and assigns the result of that command, the value of $var, to the variable $val. Although this might seem a bit obscure, you will appreciate being able to build a variable name and extract its value this way as you progress in your Tcl-writing avocation.

Notice that ShowStatus uses the puts command, which displays its output to stdout. I just want to be clear that even in a GUI program, you still have access to stdout and that the puts command does not scribble on the GUI, but on a plain old terminal session. If you start ckbutton.tcl from an icon without an underlying terminal session, the output generated by puts would be lost (unless you puts it to a file, of course).

The next section of code creates three checkbutton widgets. Nothing really extraordinary in this code block, but do notice that I give each widget a -command option, {ShowStatus name}. When you click the corresponding checkbutton, it will display its value after the click. For example, if you click the top checkbutton (see Figure 10.9), that click first selects the widget, setting its linked variable to its -onvalue (which defaults to 1), and then executes ShowStatus .cktop, which displays the new value. As a final twist, I define alternative values for the bottom checkbutton ($ckBot) selected and unselected states, OFF and ON, respectively, to demonstrate that you aren’t limited to using simple numeric values.

Tk checkbuttons.

Figure 10.9. Tk checkbuttons.

Note: Don’t Forget about Grouping!

Note: Don’t Forget about Grouping!

The -command option expects a single string argument, so you have to use braces to group arguments that include embedded spaces.

The last bit of setup code involves defining the command buttons that provide most of the application’s functionality. There are five buttons: an Exit button ($bExit); a Toggle All button ($bToggle) that invokes each checkbutton’s toggle command; a Clear All ($bClear) button that deselects each checkbutton; a Set All button ($bSet) that selects each checkbutton; and a Show All button ($bShow) that displays each checkbutton’s current selection status.

Finally, I’m ready to arrange and display the widgets. The first pack command arranges the three checkbuttons in the upper-lefthand corner of the window. I use the anchor w option to fix the checkbuttons against the “west” or left edge of the window. All of the buttons are laid out relative to the left side of the window using the -side left attribute. First, I place $bToggle with five pixels of padding on all sides. Next, I place $bClear, $bSet, $bShow, and $bExit with five pixels of padding on the top and bottom, zero padding on the left side, and five pixels of padding on the right. I don’t pad the left side of these four buttons because the padding on the right side of the previous button provides the spacing I need.

Figure 10.9 shows this script in action. Play around with the buttons while looking at the associated code so you can see how all the parts connect and interact.

Figure 10.9 shows the ckbutton.tcl script and the output created by clicking the Show All button. I ran this ckbutton.tcl from a terminal so you could see the output when you click the Show All button or one of the checkbuttons.

Radio Buttons

Unlike checkbuttons, radio buttons are usually used to select one item from a set of mutually exclusive set of items. Suppose that you want to allow users to select a color scheme, often called themes or skins, for your application. You can only apply a single color scheme at a time, so it makes sense to use radio buttons for the scheme selection interface. When a user selects one color scheme, the other color scheme radio buttons will be automatically unselected. The nice thing about Tk’s radiobutton widget is that it monitors the value of its associated variable and when the value of that variable changes, the widget automatically updates its selection status to reflect the current value of the linked variable. If, for example, the radiobutton for the Raspberry Red color scheme is selected and the user, in a fickle moment, selects the Passionate Purple radiobutton, the radiobutton for the Raspberry Red scheme automatically unselects itself. You’ll see this in action in just a moment.

Table 10.4 shows the arguments that Tk’s radiobutton widget supports.

Table 10.4. radiobutton Arguments

Option

Description

-command

Specifies the command to execute when the radiobutton is pressed.

-height

Sets the radiobutton height.

-indicatoron

Determines if the indicator is on (true) or off (false).

-selectcolor

Specifies the radiobutton’s background color when it is selected.

-offrelief

Specifies the relief if the indicator is not drawn and the radiobutton is off.

-overrelief

Specifies a non-default relief when the cursor hovers over the radiobutton.

-selectimage

Specifies the image to display when the radiobutton is selected and -image is also specified.

-state

Specifies the radiobutton’s current state, which must be one of normal, active, or disabled.

-value

Defines the value to store in the radiobutton’s associated value when the radiobutton is selected.

-variable

Sets the name of the variable associated with the radiobutton (selectedButton by default).

-width

Sets the radiobutton’s width.

cget

Returns the value of the specified option.

configure

Returns or sets the radiobutton’s configuration options.

deselect

Unselects the radiobutton and sets its associated variable to the value specified by the -offvalue attribute.

flash

Flashes the radiobutton by alternating between its active and normal colors.

invoke

Executes the Tcl command associated with the button, if one is defined.

select

Selects the radiobutton and sets its associated variable to the value specified by the -onvalue attribute.

The following script, radio.tcl in this chapter’s code directory, uses Tk’s radio buttons to implement a simple color section interface (you’ll learn more about Tk’s color handling later in this chapter):

proc SetColor {newColor} {
    . configure -background $newColor

    global lColor    $lColor configure -background $newColor

    global radButtons    foreach w $radButtons {
        $w configure -background $newColor 
            -activebackground $newColor 
            -highlightbackground $newColor    }
}

set rRed [radiobutton .rred -text "Red" -value red]
set rBlue [radiobutton .rblue -text "Blue" -value blue]
set rGreen [radiobutton .rgreen -text "Green" -value green]
set rYellow [radiobutton .ryellow -text "Yellow" -value yellow]
set rPurple [radiobutton .rpurple -text "Purple" -value purple]

set radButtons [list $rRed $rBlue $rGreen $rYellow $rPurple]

set lColor [label .lcolor -text "Select color scheme"]

set bSet [button .bset -width 8 -text "Set Color" 
   -command {SetColor $selectedButton}]
set bExit [button .bexit -width 8 -text "Exit" -command exit]

$rPurple select
$bSet invoke

pack $lColor -anchor w
pack $rRed $rBlue $rGreen $rYellow $rPurple -pady 0 -padx {2 0} -anchor w
pack $bSet -pady {10 2} -padx 5
pack $bExit -pady {2 10} -padx 5

radio.tcl uses five Tk radiobutton widgets from which you can select a color. When you click the Set button, the background color of the main window, the label, and the buttons change to the color you selected. Figure 10.10 (in black and white, so the color change is difficult to see) illustrates how it looks.

Using radiobutton widgets.

Figure 10-10. Using radiobutton widgets.

The SetColor procedure takes a single argument: the new color for the main window, the label, and the buttons. The first command sets the background color of the main window. After declaring the lColor global variable, I set its background color. I use a foreach loop to set the background color for each of the radiobutton widgets. Rather than passing in a list of widgets like I did in ckbutton.tcl, I declare the $radButtons variable as a global. I set three background attributes for each radiobutton to ensure that the color is correct, regardless of its state. The -background attribute covers the normal state, enabled but not active. It will come as no surprise that -activebackground covers the state when the radiobutton is active, that is, when the mouse is hovering over the button and clicking (selecting, in this case) the button. The -highlightbackground attribute colors the rectangle that appears around a widget when it is active.

After I define the SetColor helper procedure, I start laying out the widgets. First, I define five radiobutton widgets, one each for red, blue, green, yellow, and purple and a list of radiobutton widgets ($radButtons). For each button, I use the -value color attribute. This attribute sets the value that is assigned to the linked variable when the radiobutton is selected. You’ll no doubt notice that I don’t specify the name of the linked variable. If no variable name is specified (using -variable varname), the name defaults to selectedButton. While one could argue that it would be better to use a special variable name for uniqueness and easy identification, this script is short enough that using the default name is acceptable.

I then create a label, $lColor, to give users a hint what the radio buttons do, followed by two buttons, $bset and $bExit, to set the selected color or exit the script, respectively. The Exit button needs no comment. The Set Color button simply invokes the SetColor procedures, passing the $selectedButton variable to the procedure. Be careful here! Although the variable’s name is $selectedButton, the value it holds is the name of the color that will be set. It is precisely this potential confusion that makes defining my own variable instead of using the default variable a good idea. I kept the default name for a pedagogical reason (not to mention plain laziness), namely, I wanted you to know that radiobutton widgets have a default variable and what it is named.

Before displaying the widgets, I set a bit of initial state. The $rPurple select command sets the state of the purple radiobutton to be selected, which means that $buttonSelected has the value purple. $bSet invoke programmatically clicks the Set Color button to set the color to the one I just selected, which is purple. Bear in mind that the results of these commands won’t be visible until I explicitly draw the window with pack.

To display my GUI masterpiece, I first draw the label, anchoring it to the left side of the window. Next, I stack the five radiobutton widgets one on top of the other, again, anchoring them to the left side of the parent window. When I pack the Set Color button, I use a large amount of vertical padding to visually separate it from the radio buttons above it. For a similar but inverse reason, I pack the Exit button with very little vertical padding between it and the Set Color button. The purpose of this visual grouping is to relate the two command buttons to each other and to distinguish them from the radio buttons.

A Smörgåsbord of Menus

A GUI without menus is like a day without sunshine. Or not. Regardless, menus are an important part of any graphical application, and Tk has excellent support for the full range of menu operations. To create a menu, you use the menu widget to create a menu entry and then use additional commands to add entries to the menu widget. Tk supports a variety of menu features, including:

  • Cascading menus, which allow you to display sub-menus.

  • Check entries, which resemble checkbutton widgets.

  • Command entries, which resemble button widgets.

  • Option menus, which display a set of choices using radiobutton widgets.

  • Pop-up menus, normally displayed in response to a right-click.

  • Radio entries, which resemble radiobutton widgets.

  • Separators, providing visual separation between menu entries.

  • System menus, which add entries to the existing Windows system menu, the Macintosh Apple menu, and the Help menu on any supported platform.

  • Tear-off entries, which allow users to detach, or tear off, a menu from its parent.

To do full justice to Tk’s menu support would require a full chapter and more space than I have, so I’ll focus in this section on creating a menu bar that contains commands and separators. This should meet your immediate menu-creation needs, provide a template for creating complete, full-featured menus, and give you a sense of Tk’s capabilities with respect to menus.

Creating a Basic Menu Bar

The following script, basic_menu.tcl in this chapter’s code directory, shows the steps you need to follow to add a menu to your application:

set mainMenu [menu .mainmenu]

. configure -menu $mainMenu

set mFile [menu $mainMenu.mFile -tearoff 0]
$mainMenu add cascade -label "File" -menu $mFile

$mFile add command -label "Open" -command {DoCmd Open}
$mFile add command -label "Close" -command {DoCmd Close}
$mFile add command -label "Save as" -command {DoCmd "Save as"}
$mFile add command -label "Save" -command {DoCmd Save}
$mFile add separator
$mFile add command -label "Exit" -command exit

proc DoCmd {cmd} {
tk_messageBox -icon info -type ok -message $cmd
}

The resulting window should resemble Figure 10.11.

With a minimal amount of Tk code, you can create a functional menu.

Figure 10.11. With a minimal amount of Tk code, you can create a functional menu.

If you click the File item on the menu, the figure you see should look like Figure 10.12.

Clicking the File item opens the associated menu.

Figure 10.12. Clicking the File item opens the associated menu.

The first command, set mainMenu [menu .mainmenu], creates a menu entry. It doesn’t have any content, but I’ll take care of that in a moment. The next command, . configure -menu $mainMenu, associates the menu I just created with the root window, meaning that the menu entry referenced by $mainMenu is the menu bar for the root window. The next two lines of code add an item to the menu bar. In this case, I’m creating a “File” menu. First, I create a second menu entry named $mainMenu.mFile, which establishes the widget mFile as a child of the main menu—this relationship is necessary so that the mFile menu will display properly when users click the File item on the menu bar. The attribute -tearoff 0, tells Tk that this menu cannot be detached from the menu bar (the default value is -tearoff 1, which permits detaching, or tearing off the menu, to create a new top-level window). The second command, $mainMenu add cascade -label "File" -menu $mFile, adds an item named File to the menu bar (add cascade -label "File"), which, when clicked, opens the menu specified by $mFile (-menu $mFile).

The next block of code adds items to the newly created File menu. Specifically, I add an Open command, a Close command, a Save as command, a Save command, a separator, and an Exit command. The separator is merely a visual aid. Each of the command entries consists of a label and an associated command to carry out the requested operation. In this case, the command is DoCmd label, where label is the text. DoCmd itself is a simple procedure that uses the tk_messageBox procedure to pop up a window that displays the text passed to DoCmd. The exception is the Exit item, which invokes Tcl’s exit command to terminate the script.

To add another menu item, say, Edit, to the menu bar, you would create a second menu item and populate it with commands, for example:

set mEdit [menu $mainMenu.mEdit -tearoff 0]
$mainMenu add cascade -label "Edit" -menu $mEdit

$mEdit add command -label "Copy" -command {DoCmd "Copy"}
$mEdit add command -label "Paste" -command {DoCmd "Paste"}
$mEdit add command -label "Cut" -command {DoCmd "Cut"}
$mEdit add command -label "Search" -command {DoCmd "Search"}
$mEdit add command -label "Replace" -command {DoCmd "Replace"}

Figure 10.13 shows what the resulting menu would look like.

Use the code in basic_menu.tcl as a template for creating your own menus.

Figure 10.13. Use the code in basic_menu.tcl as a template for creating your own menus.

As you can see, creating a Tk menu isn’t difficult at all.

Binding Commands to Events

In simplest terms, binding a command to an event means to arrange for a command or procedure or script to execute when an event occurs. The -command attribute of most Tk widgets does precisely that, it binds or ties the specified command to a particular event, such as selecting a radio button or pressing a command button. Not surprisingly, most events in a Tk application are not tied to pressing keys or mouse clicks. Rather, events occur, quite literally, all the time. Clicking on a window to give it the focus is an event; moving the mouse cursor across a window generates mouse movement events and events when the mouse enters and leaves the window; creating and deleting windows are events; resizing a window is an event; even changing a window’s visibility is an event.

Tk, like most GUI frameworks, provides a method for hooking into these (and other) events to bind a command, procedure, or script to them. The command that accomplishes this is the aptly named bind command. Its syntax is:

bind tag ?event? ?+??cmd?

This command binds the command specified by cmd to the event specified by event that happens to the widget or GUI element specified by tag. To put it more directly, when tag’s event occurs, cmd executes. Table 10.5 lists the events that you can bind.

Table 10.5. Bindable Events

Event

Description

Activate

Occurs when a window is activated, usually by receiving focus.

ButtonPress,

Generated when a mouse button is pressed; ButtonPress and Button are

Button

synonyms.

ButtonRelease

Occurs when a mouse button is released.

Configure

Occurs whenever a window is moved, resized, or its border width changes.

Deactivate

Occurs when a window is deactivated, usually by losing focus.

Destroy

Generated when a window is destroyed, usually by closing or deleting it.

Enter

Delivered to the window that the mouse pointer enters.

Expose

Generated when a window must be redrawn after being uncovered or drawn for the first time.

FocusIn

Sent to a window receiving keyboard input focus.

FocusOut

Sent to a window losing keyboard input focus.

KeyPress, Key

Generated whenever a key is pressed; KeyPress and Key are synonyms.

KeyRelease

Generated whenever a key is released.

Leave

Delivered to the window that the mouse pointer is exiting.

Map

Generated when a window is made viewable by being mapped onto the screen (not minimized or iconified).

Motion

Occurs whenever the mouse pointer is moved.

MouseWheel

Delivered to a window with input focus when a wheel on a mouse is scrolled up or down or clicked.

Unmap

Generated when a window is iconified, minimized, or removed from the active screen.

Visibility

Occurs when a window’s visibility changes, such as when another window is moved over it and obscures it or when an obscuring window is moved out of the way.

My discussion of event binding and the bind command covers the simplest case, binding a single command or procedure to a single widget. The topic is considerably more sophisticated than I suggest in this section. The goal of this section was to show you that the capability exists, rather than to show you how to use it. I consider it an advanced topic beyond this book’s limited scope because it is one of the most complicated parts of Tk programming. I don’t want to overwhelm you with minutiae that you might not use for a long time. If you need more information, the bind command’s man page (man 3tk bind) has all of the gory details.

Coloring Your World

The Tk widget attributes that set and modify colors, such as -activebackground and foreground, accept colors in one of two formats: a color name, such as red, blue, or green, or their RGB value specified in hexadecimal (hex) format, such as #f00, #0f0, or #0ff (which are red, green, and blue, respectively, specified in 8-bit hex values). The color names that Tk supports are documented in the colors man page (man 3tk colors). On UNIX, Linux, and BSD systems, you should be able to view the complete list of colors using the showrgb command (part of the X Window System, not Tcl or Tk), shown in the following excerpt, which displays a variety of colors according to their decimal RGB value and their name:

$ sshowrgb
255 250 250              snow
248 248 255              ghost white
248 248 255              GhostWhite

...
139   0   0              DarkRed
144 238 144              light green
144 238 144              LightGreen

The complete color list has 752 entries (admittedly, some are duplicates, like ghost white and GhostWhite), so you’ll pardon me for not showing the entire list. These color names are derived from Tk’s origins on the X Window System and are supported on all platforms to which Tk has been ported.

If you choose to use the hex format, you can specify each component in 4-, 8-, 12-, or 16-bit format. 4-bit hex values use a single digit for each component; 8-bit hex values use two hex digits; 12-bit hex values use three; and 16-bit hex values use four. Thus, you can specify the color red as #f00, #ff0000, #fff000000, or #ffff00000000.

As a bonus, I’ve included a simple Tk application, show_colors.tcl in this chapter’s code directory, which shows the colors available in the stock Tk distribution. It shows a listbox that contains all of the predefined colors (well, the color names) that Tk supports. You select a color from the listbox and click the Set Color button to update a color swatch (actually, a label widget) with the currently selected color. Figure 10.14 shows what this script looks like.

Use show_colors.tcl to view Tk’s default color palette.

Figure 10.14. Use show_colors.tcl to view Tk’s default color palette.

I won’t describe show_colors.tcl’s code because it uses the listbox widget, which you don’t learn to use until Chapter 13.

Analyzing Memory Test

memtest.tcl is the longest script I’ve created (so far) in this book. Most of it consists of procedure definitions and setup code; the actual execution is pretty simple.

Looking at the Code

#!/usr/bin/wish
# memtest.tcl
# Play a Simon-like memory game

# Block 1
# Randomly select a button from a list of buttons
proc SelectButton {buttons} {
    set index [expr {int(rand() * [llength $buttons])}]
    return [lindex $buttons $index]
}

# Flash randomly selected buttons count times
proc FlashButtons {buttons count} {
    # Create a list of buttons to flash
    for {set i 1} {$i <= $count} {incr i} {
        set btn [SelectButton $buttons]
        lappend flashList $btn
    }

    # Flash the buttons in the list
    foreach btn $flashList {
        $btn flash
        $btn flash
        after 1000
    }

    return $flashList
}

# Compare the flashed buttons to players buttons and display
# the results
proc Score {flashedButtons pressedButtons} {
    global bScore
    global lInfo

    # Disable the score button
    $bScore configure -state disabled

    # Compare the buttons
    if {[llength $flashedButtons] != [llength $pressedButtons]} {
        $lInfo configure -text "Sorry, wrong sequence!"
        return
    }

    if {[lindex $flashedButtons] ne [lindex $pressedButtons]} {
        $lInfo configure -text "Sorry, wrong sequence!"
        return
    }
    # Only get here if the sequences match
    $lInfo configure -text "You did it!"
    return 0
}

# Block 2
# Play the game
proc PlayGame {buttons} {
    global lInfo
    global bPlay
    global bScore

    # Disable the Play button
    $bPlay configure -state disabled

    # Flash the buttons
    set flashedButtons [FlashButtons $buttons 4]

    # Enable the Score button
    $bScore configure -state normal

    # Tell the player what's next
    $lInfo configure -text "Try to repeat the
sequence. Then press
Score to see how
    you
did."
    return $flashedButtons
}

# Block 3
# Game buttons
set bRed [button .bred -background red4 -activebackground red 
    -command {lappend pressedButtons $bRed}]
set bBlue [button .bblue -background blue4 -activebackground blue 
    -command {lappend pressedButtons $bBlue}]
set bGreen [button .bgreen -background green4 -activebackground green 
    -command {lappend pressedButtons $bGreen}]
set bYellow [button .byellow -background yellow4 -activebackground yellow 
    -command {lappend pressedButtons $bYellow}]
set bCyan [button .bcyan -background cyan4 -activebackground cyan 
    -command {lappend pressedButtons $bCyan}]

# List of game buttons
set buttons [list $bRed $bBlue $bGreen $bYellow $bCyan]

# Command buttons
set bPlay [button .bplay -text Play 
    -command {set flashedButtons [PlayGame $buttons]}]
set bScore [button .bscore -text Score -state disabled 
    -command {Score $flashedButtons $pressedButtons}]
set bExit [button .bexit -text Exit -command exit]

# Instruction label
set lInfo [label .linfo -width 20 -height 4 -justify left 
    -text "Press Play to start"]

# Display
pack $bRed -pady {5 0} -expand true -fill x
pack $bBlue $bGreen $bYellow $bCyan $lInfo -expand true -fill x
pack propagate $lInfo false
pack $bPlay $bScore $bExit -pady {20 0} -side left -expand true -fill x

Understanding the Code

As usual, most of the code appears in the procedure definitions that make up Block 1 at the top of the file. The first procedure, SelectButton, selects a button at random from a list of buttons passed to it in the argument buttons and returns the name of that button to the calling procedure.

FlashButtons flashes each button in the list of buttons passed to it as an argument in buttons. The count argument indicates how many buttons to flash. The for loop creates the list of buttons to flash by calling SelectButtons $count times and appending the button name to the $flashList variable. The foreach loop iterates through $flashList, flashing each button twice and then pausing 1000 milliseconds (after 1000), or one second, to create a delay before flashing the next button. After flashing all of the buttons, FlashButton terminates, returning the list of flashed buttons to the calling procedure.

The next procedure, Score, compares the list of buttons the computer generated, passed in flashedButtons, to the list of buttons the player pressed, passed in pressedButtons. First, I disable the Score button to prevent the user from pressing it multiple times and to give the player a visual cue that the game is almost over. I perform two comparisons. If the length of the two lists is different, I know the lists don’t match and can terminate the procedure without needing to compare each button. If the lengths match, the second if statement compares the lists of buttons in their entirety. If the strings returned by the two lindex statements don’t match, the procedure terminates. Otherwise, the two lists match, and the player has won. In all cases, I update a label in the game window showing the player whether she won or lost.

Block 2 consists of the PlayGame procedure, which handles the bulk of the gameplay. It starts by disabling the Play button, then calls the FlashButtons procedure, passing it the list of buttons that was passed to PlayGame and the number of buttons to flash. I save the list of buttons returned by FlashButtons in the $flashedButtons variable so I can return that list to the main game loop. After all of the buttons are flashed, I enable the Score button and then instruct the player to try to replicate the sequence of button presses.

In Block 3, I start creating the game board. First, I create five colored buttons. Their -command attributes create entries in the list of buttons pressed by the user (stored in pressedButtons). Each button’s background color is a darker shade of its active background color. When pressed, this arrangement of colors makes the button noticeably easier to distinguish in its flashing state. I create a list of these game buttons to pass to the various procedures.

The command buttons, naturally, do the work in the program. The Play button, $bPlay, invokes the PlayGame procedure when pressed. The Score button, $bScore, starts out in the disabled state. As described earlier, it will be enabled after the buttons are flashed. This is largely a visual cue to the player to facilitate gameplay, but also serves to prevent unpleasant events if the player presses the Score button while the game buttons are being flashed. $bScore calls the Score procedure, passing the two lists of buttons to Score for evaluation. The Exit button is nothing new and enables the player to exit the game at any time. The instruction label, $lInfo, just provides an area where I can display text telling the player how to proceed.

The final lines of code lay out the widgets on the screen. I pack the red button first, to set a more aesthetically pleasing distance between the top of the button and the top edge of the window. Next, I pack the rest of the buttons and the information label in below the red button. The attributes -expand true -fill x cause the widgets to expand horizontally to fill their parcels in the window. I pack the Play, Score, and Exit buttons across the window with sizable padding between the instruction label and the tops of the command buttons. While this is partly an aesthetic preference on my part—I don’t like windows with widgets jammed together—it is also yet another visual hint to the player that separates the elements of the game to suggest that they have different functions.

With just a few widgets, you can write a reasonably complete Tk application. buttons, radiobuttons, and checkbuttons are common UI elements that enable users to invoke actions and make choices. Knowing how to set and modify colors in a user interface is important because humans use colors as visual cues. Nonetheless, no matter how visually rich and widget-packed you make your user interface, the core of any Tk application is still the Tcl code that ties UI elements to commands and procedures and that provides the logic that makes the application work.

Modifying the Code

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

  • 10.1 Modify the script to use fewer global variables.

  • 10.2 Modify the Score procedure to perform a single definitive comparison of the flashed and pressed buttons.

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

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