7—
The Listbox Widget
0141-01.gifA listbox widget is designed to list strings of text, one text string per line. You can then select a line or multiple lines from the listbox to perform other operations on. Some examples of things to place inside a listbox:
• An alphabetized list of cities.
• A list of servers to log in to. Select a server name and then enter a name and password into some entry widgets. Click the OK button to log in.
• A list of operating systems.
• A list of payment options: MasterCard, American Express, Visa, Check, Cash.
A listbox is ideal for replacing radiobuttons or checkboxes that have become too numerous to display on the screen. Usually 3 or 4 checkbuttons or radiobuttons aren't a big deal, but if you had to try to display 10 at a time, the window could get a little crowded. A group of radiobuttons can be replaced by a listbox that limits the number of selections to one and has a default selection. A bunch of checkbuttons can be replaced by a listbox that allows multiple selections.
Creating and Filling a Listbox
To create a listbox widget, use the Listbox method on the parent of the listbox:
$lb = $parent->Listbox( [ options...] )->pack;
The Listbox method returns a reference to the listbox that has been created. You can now use this reference to configure the listbox, insert items into the listbox,
and so on. The most common thing to do after creating a listbox is to use the insert method to insert items into it:
$lb->insert('end', @listbox_items);
# or...
$lb->insert('end', $item1, $item2, $item3);
The insert method takes an index value as the first argument; the rest of the arguments will be considered items to be put into the listbox. Listbox indexes are similar to the entry widget indexes except they refer to lines instead of individual characters.
We could use a listbox instead of radiobuttons to select our window background color (see Chapter 4, Checkbuttons and Radiobuttons, for the radiobutton example). The listbox code looks like this:
$lb = $mw->Listbox(-selectmode => "single")->pack();
$lb->insert('end', qw/red yellow green blue grey/);
$lb->bind('<Button-1>',
          sub { $lb->configure(-background =>
                              $lb->get($lb->curselection() ) );
              });
The -selectmode option limits the number of selections to one. We insert some colors to choose from. There is no -command option for a listbox, so we use bind (see Chapter 14, Binding Events) to have something happen when the user clicks on an item with the left mouse button. Using the listbox methods get and curselection, we determine which item the user clicked on and then set the background of the listbox to that color. There are only five colors in our example here; you can use more colors and add a scrollbar to make it more useful. You can add a scrollbar by changing the line with Listbox in it:
$lb = $mw->Scrolled("Listbox", -scrollbars => "e",
                    -selectmode => "single")->pack();
All the other lines in the program remain unchanged. For more information about adding and utilizing scrollbars, see Chapter 6, Scrollbars. Now that we've looked at an example, let's go over the options and methods that let us use the listbox the way we want to.
Listbox Options
As with any of the widgets, you can configure the listbox using options. The standard widget options are -cursor, -font, -height, -highlightbackground, -highlightcolor, -highlightthickness, -takefocus, -width, -xscroll-command, and -yscrollcommand. The options that behave the same for each widget will only be listed in the following list. Those options specific to listbox widgets will be discussed later in this chapter.
-background => color
Sets the color of the area behind the text.
-borderwidth => amount
Sets the width of the edges of the widget. Default is 2.
-cursor => cursorname
Sets the cursor to display when the mouse is over the listbox.
-exportselection => 0 | 1
Determines if the current listbox selection is made available for the X selection as well. If set to 1, prevents two listboxes from both having selections at the same time.
-font => fontname
Sets the font of any text displayed within the listbox.
-foreground => color
Sets the color of nonselected text displayed in the listbox.
-height => amount
Sets the height of the listbox.
-highlightbackground => color
Sets the color the highlight rectangle should be when the listbox does not have the keyboard focus.
-highlightcolor => color
Sets the color the highlight rectangle should be when the listbox does have the keyboard focus.
-highlightthickness => amount
Sets the thickness of the highlight rectangle. Default is 2.
-relief => 'flat' | 'groove' | 'raised' | 'ridge' | 'sunken' | 'solid'
Sets the relief of the edges of the listbox.
-selectbackground => color
Sets the color behind any selected text.
-selectborderwidth => amount
Sets the width of the border around any selected text.
-selectforeground => color
Sets the color of the text in any selected items.
-selectmode => "single" | "browse" | "multiple" | "extended"
Affects how many items can be selected at once; also affects some key/mouse bindings for the listbox (such as Shift-select). Default is "browse".
-setgrid => 0 | 1
Turns gridding off or on for the listbox. Default is 0.
-takefocus => 0 | 1 | undef
Determines the ability of the widget to get the keyboard focus or not. 0 means never, 1 means always, undef means dynamic decision.
-width => amount
Sets the width of the listbox in characters. If amount is 0 or less, the listbox is made as wide as the longest item.
-xscrollcommand => callback
Assigns horizontal scrollbar to widget. See Chapter 6.
-yscrollcommand => callback
Assigns vertical scrollbar to widget. See Chapter 6.
Selection Modes
As part of the listbox widget, you are given several choices in the way you can select items in the listbox. You can have it so only one item at a time can be selected (emulating radiobuttons), or you can have many different contiguous or noncontiguous items selected (emulating checkbuttons). You control this behavior with the -selectmode option.
The possible select modes are "browse", "single", "multiple", or "extended". The default mode is "browse".
browse & single
The "browse" and "single" modes are similar in that only one item can be selected at a time; clicking on any item will deselect any other selection in the listbox. The browse mode has a slight difference: when the mouse is held down and moving around, the selection moves with the mouse. For bind purposes, a "<Button-1>" bind will be invoked when you first click down. If you want to catch the event when the mouse is released, define a Button-Release binding (binding events to widgets is discussed in Chapter 14).
extended
The "extended" mode lets you select more than one item at a time. You can click on a single item with the left mouse button, but it will deselect any other selection. To select more than one item, you must Shift-click or Control-click more items. Shift-clicking (holding down the Shift key while pressing a mouse button) will extend the selection from the already selected item to the newly selected item. Control-clicking (holding down the Control key while pressing a mouse button) will add the item being clicked on to the selection, but it won't alter any of the other selections. You can also click an item with the mouse button, hold down the button, and then move the pointer over other items to select them. This is what I call a click-drag motion. Using "extended" allows for very fast selection of many different items in the listbox.
multiple
The "multiple" mode also allows you to select more than one item. Instead of Shift-clicking or Control-clicking, you have to select items one at a time. Selecting an unselected item will select it, and selecting an already selected item will unselect it.
Operating System Differences
When testing the -selectmode feature, I discovered that Windows 95 does not allow the "multiple" selection mode to behave properly. It behaves the same as "single" mode on Windows 95 only. On Unix and Windows NT, "multiple" mode works correctly.
When you select an item in a listbox, by default it is made available as an X selection (meaning you can cut and paste it like any X selection in any window). Even though this doesn't do anything with the clipboard on Win32 systems, it still affects the selection in multiple listboxes. Items can be selected in only one listbox at a time, even if you have more than one listbox. The option -export-selection controls this. Use -exportselection => 0 to allow items to be selected in more than one listbox at the same time.
Colors
In most widgets there is a -background and a -foreground color. In addition to those, we also have the -selectbackground and the -selectforeground color options in a listbox. When a listbox entry is selected, it appears in a different color.
Although you can change the color of the selected text, you can only use one color. You cannot make different lines in the listbox different colors.
0145-01.gif
Figure 7-1.
Examples of -foreground, -background, -selectforeground, and -selectbackground
In Figure 7-1, the listbox on the left has -foreground => 'red', -background => 'green'. The listbox on the right has -selectforeground => 'red', -selectbackground => 'green'. Make sure that the foreground and background values contrast with each other if you change these options.
Listbox Style
The default -relief of a listbox is 'sunken'. The default -borderwidth is 2. Figure 7-2 shows the five different relief types (flat, raised, ridge, groove, and sunken). In the first window, the default -borderwidth is used; in the second window, a -borderwidth of 4 is used. To save space in the windows, I didn't draw any scrollbars.
0146-01.gif
Figure 7-2.
Examples of -relief and -borderwidth in listboxes
Style of Selected Items
There is also a borderwidth associated with any selected text. This is controlled by the -selectborderwidth option. Figure 7-3 shows what changing the selection borderwidth to 4 does to the listbox.
0146-02.gif
Figure 7-3.
Example of -selectborderwidth => 4
Special Listbox Resizing
The -setgrid option changes how the window is drawn when it's resized. Using -setgrid => 1 causes the window to stay resized to the grid created by the listbox widget. Essentially, this means that the listbox will display only complete lines (no half lines) and complete characters. A side benefit is that the listbox will always display at least one line and can't get resized off the visible window. This option has nothing to do with which geometry manager you use to put the listbox in the window.
Listbox Indexes
The items in an entry widget are ordered. The first listbox item is at index 0, and the numbers increment by 1. These values are valid for any of the methods that require an index value.
n
An integer index. The first item in a listbox is at index 0.
"active"
The index within the listbox that has the location cursor. If the listbox has the keyboard focus, it will be displayed with an underline.
"anchor"
This index is set with the selectionAnchor (...) method.
"end"
The end of the listbox. Depending on which method is using this index, it could mean just after the last element (such as when insert is used), or it could mean the last element in the listbox (such as when delete is used).
"@x,y"
The listbox item that covers the point at the coordinate x,y (pixel coordinates). The closest item will be used if x,y is not at a specific item.

Configuring a Listbox
You can use the cget method to find out the current value of any of the listbox options. You can use configure to query or set any of the listbox options. See Appendix A, Configuring Widgets with configure and cget, for more information on using the configure and cget methods.
Inserting Items
Use the insert method to add items to the listbox:
$lb->insert(index, element, element ... );
Each element is another line in the listbox. The index is a valid index (see ''Listbox Indexes" later in this chapter) that the new elements will be inserted before. For instance, to insert items at the end of the listbox:
$lb->insert('end', @new_elements);
# Or
$lb->insert('end', "Item1", "Item2", "Item3");
To insert items at the beginning of the listbox:
$lb->insert(0, @new_elements);
Deleting Items
You can use the delete method to delete items from the listbox:
$lb->delete(firstindex [, lastindex ]);
The first argument is the index from which to start deleting. To delete more than just that one item, you can add a second index. The firstindex must be less than or equal to the lastindex specified. To delete all the elements in the listbox:
$lb->delete(0, 'end');
To delete the last item in the listbox:
$lb->delete('end');
Retrieving Elements
The get method returns a list of listbox elements specified by the indexes first to last:
$lb->get(firstindex [, lastindex ]);
If only the firstindex is specified, only one element is returned. The firstindex must be less than or equal to the lastindex. To get a list of all elements in the listbox:
@elements = $lb->get(0, 'end');
To get the last item in the listbox:
$lastitem = $lb=>get('end');
To find out which items in the listbox are selected, use the curselection method:
@list = $lb->curselection();
It returns a list containing the indexes of all currently selected items in the listbox. If no items are selected, curselection returns an empty string. Here is an example of how the curselection method is used:
@selected = $lb->curselection;
foreach (@selected) {
  # do something with the index in $_
}
Make sure to remember that curselection returns a list of indexes, not elements.
Selection Methods
The curselection method, discussed in the preceding section, only tells you what the user has selected. You can also change the selection by using a form of the selection method.
Selecting Items
To select a range of items in a listbox, you can use the "set" form of the selection method (selectionSet). selectionSet takes either a single index or a range. Any items not in the range are not affected. If you use a range, the first index must be less than or equal to the last index. Here are some examples:
# select everything
$lb->selectionSet (0, 'end' );
#select the first item
$lb->selectionSet (0);
Even if you have used -selectmode to limit the selection to only one item, you can force more than one item to be selected by using selectionSet (...).
Unselecting Items
To clear any selections in the listbox, use the "clear" form of the selection method (selectionClear). Pass in an index or a range or indexes from which to clear the selection. For instance, to remove all the selections in the listbox, you would do the following:
$lb->selectionClear (0, "end");
Any indexes outside the specified range will not be unselected-this allows you to unselect one item at a time. You can also clear the selection from just one item:
$lb->selectionClear ("end");
Testing for Selection
To test to see if a specific index is already selected, use the "includes" form of selection (selectionIncludes). Calling selectionIncludes returns 1 if the item at the specified index is selected and 0 if it is not. For instance, to see if the last item in the list is selected:
if ($lb->selectionIncludes('end') ) {
  ...
}
Anchoring the Selection
Using the "anchor" form of selection (selectionAnchor) to set the index "anchor" to the specified index. The "anchor" is used when you are using the mouse cursor to select several items within the listbox. The first item you click (without letting up on the mouse button) becomes the "anchor" index. For example, you would use this to set the anchor as the first item in the list:
$lb->selectionAnchor(0);
Moving to a Specific Index
To cause the listbox to show a specific item, you can use the see method.
$lb->see(index);
Given an index, see will cause the listbox to page up or down to show the item at that index. For an example of using see, look at the Listbox Example later in this chapter.
Translating Indexes
The index method translates an index specification (such as "active") into the numerical equivalent. For instance, if the listbox contained 12 items, $index = $lb->index ("end") would set the variable $index to 11. (Remember the first item in a listbox is at index 0.)
Counting Items
The size method returns the total number of items in the listbox:
$count = $lb->size();
Active Versus Selected
The activate method will set the listbox item at index index to the active element. This allows you to access this item later using the "active" index. Figure 7-4 shows two windows with active elements underlined. Each listbox also has the black highlight rectangle around it, which indicates it has the keyboard focus (the active element isn't seen as marked unless the listbox has focus).
# The first window activates the item "four"
$lb->activate (3);
$lb->focus();
# The second window activates the item "three"
$lb2->activate(2);
$lb2->focus();
0151-01.gif
Figure 7-4.
Windows showing a listbox with an "active" element
Bounding Box
The method bbox returns a list of four elements that describes the bounding box around the text at index:
($x, $y, $w, $h) = $lb->bbox(index);
The four elements are (in order): x, y, w, and h. The x,y coordinates are the upper left corner of the bounding box. The w is the width of the text in pixels. The h is the height of the text in pixels. These measurements are shown in Figure 7-5.
0151-02.gif
Figure 7-5.
Bounding box values around text
Finding  an Index by Y Coordinate
If you know a y coordinate in the listbox, you can determine the index of the nearest listbox item to it by using the nearest method:
$index = $lb->nearest(y)
The nearest method returns a number that corresponds to the index of the closest visible listbox item.
Scrolling Methods
The listbox can be scrolled both horizontally and vertically so it has both xview and yview methods and all their associated forms. These forms and how to use them are described in detail in Chapter 6.
The scan method allows you to use a really fast scrolling method. It is automatically bound to the second mouse button by the listbox. Here is how you can do the same thing within your window:
$mw->bind("Listbox", "<2>", ['scan', 'mark', Ev('x'),Ev('y')]);
$mw->bind("Listbox", "<B2-Motion>", ['scan', 'dragto', Ev('x'),Ev('y')]);
When you click in the window with the second mouse button and then move your mouse around, you'll see the contents of the listbox zip by at super-fast speed. You could change the second argument of each bind statement if you wanted to bind this to another combination of keys/mouse actions. The bind method is explained in Chapter 14.
Listbox Example
Sometimes when you put a lot of items in a listbox, it takes a long time to scroll through the listbox. If you insert the items in the listbox sorted, you can implement a search routine. Here's a quick script that shows you how to use an entry widget to input the search text and then search the listbox every time you get a new character in the entry:
use Tk;

$mw = MainWindow->new;
$mw->title("Listbox");
# For example purposes, we'll use one word for each letter
@choices = qw/alpha beta charlie delta echo foxtrot golf hotel india
              juliet kilo lima motel nancy oscar papa quebec radio sierra
              tango uniform victor whiskey xray yankee zulu/;

# Create the entry widget, and bind the do_search sub to any keypress
$entry = $mw->Entry(-textvariable => \$search)->pack(-side => "top",
                                                     -fill => "x");
$entry->bind("<keyPress>", [ \&do_search, Ev("K") ]);

# Create listbox and insert the list of choices into it
my $lb = $mw->Scrolled("Listbox", -scrollbars => "osoe",
                       )->pack(-side => "left");
$lb->insert("end", sort @choices);

$mw->Button(-text => "Exit",
            -command => sub { exit; })->pack(-side => "bottom");

MainLoop;

# This routine is called each time we push a keyboard key.
sub do_search {
  my ($entry, $key) = @_;
 
  # Ignore the backspace key and anything that doesn't change the word
  # i.e. The Control or Alt keys
  return if ($key =~ /backspace/i);
  return if ($oldsearch eq $search);

  # Use what's currently displayed in listbox to search through
  # This is a non-complicated in order search
  my @list = $lb->get(0, "end");
  foreach (0 .. $#list) {
    if ($list[$_] =~ /^$search/) {
      $lb->see($_);
      $lb->selectionClear(0, "end");
      $lb->selectionSet ($_);
      last;
    }
  }
  $oldsearch = $search;
}
Fun Things to Try
Use a listbox to create a mini file viewer. Use an entry field to read a filename and a button that, when you click on it, loads the file into your listbox (each line in the file becomes one entry in the listbox).