List Boxes

Another type of simple input control is the list box. The Toolbox offers three types of list boxes: the combo box, the list box, and the checked list box. The examples in this section demonstrate the combo box and list box controls.

ComboBox Control

The combo box control is the simplest of the list box control types. The class ComboBox displays a single-line, read-only box with an arrow on the right. When the user clicks the arrow, the combo box drops down a list of objects the user can pick from. When the user chooses from the list, it disappears and the selected object is displayed in the combo box output window.

The combo box list can be static or calculated. The values for a static list are known at the time that you write the code. You can use the Properties window to specify the items in a static list. The values for a calculated list are determined at run time by the app.

Displaying a static list of strings

The simplest combo box is one that contains a static list of String objects. The following example app, ComboBox1, demonstrates a ComboBox object containing the months of the year. When the user selects one of the months, the app displays the selection in the combo box and in a separate edit box immediately above the combo box. The output from the resulting app is shown in Figure 6-4.

Screenshot

Screenshot-4. The ComboBox1 demo showing the months of the year displayed in a combo box list.

The Forms Designer work to create ComboBox1 is straightforward. First drag an Edit control to the Forms Designer and set its readOnly property to true. Next drag a ComboBox control from the Toolbox to the Forms Designer and change its name property to inputComboBox. I set both objects' font properties to 14-point boldface to make the display stand out better. Adding a couple of labels almost completes the Forms Designer work.

The final task in the Forms Designer is to add the months of the year to the combo box list. To do this, select the items property of the ComboBox control in the Properties window. Clicking the ellipsis button in the property setting box for this property opens the String List Editor. Enter the months of the year as shown in Figure 6-5.

Java Click to view at full size.

Screenshot-5. Entering static strings into the combo box using the String List Editor.

Now all that is left to do for the ComboBox1 app is to add an event handler to copy the month selected in the combo box to the output edit box. Double clicking the ComboBox control in the Forms Designer creates the method inputComboBox_selectedIndexChanged(). Add the functionality to this event handler as shown in the following code listing of ComboBox1:

import com.ms.wfc.app.*;
import com.ms.wfc.core.*;
import com.ms.wfc.ui.*;
import com.ms.wfc.html.*;
/**
 * This class displays a static combo box.
 */
public class Form1 extends Form
{
 public Form1()
 {
 // Required for Visual J Plus Plus Form Designer support
 initForm();
 }
 /**
 * Form1 overrides dispose so it can clean up the
 * component list.
 */
 public void dispose()
 {
 super.dispose();
 components.dispose();
 }
 /**
 * This method is invoked when the user selects a month
 * from the combo box.
 */
 private void inputComboBox_selectedIndexChanged(Object source,
 Event e)
 {
 // get the index of the selected object
 int index = inputComboBox.getSelectedIndex();
 // get a list of the objects contained in the combo box
 Object[] list = inputComboBox.getItems();
 // pick the one that was selected, and convert it into a string
 String s = list[index].toString();
 // display the resulting string in the output edit window
 outputEdit.setText(s); }
 /**
 * NOTE: The following code is required by the Visual J Plus Plus form
 * designer. It can be modified using the form editor. Do not
 * modify it using the code editor.
 */
 Container components = new Container();
 Edit outputEdit = new Edit();
 ComboBox inputComboBox = new ComboBox();
 Label label1 = new Label();
 Label label2 = new Label();
 private void initForm()
 {
 this.setText("ComboBox Demo");
 this.setAutoScaleBaseSize(new Point(5, 13));
 this.setClientSize(new Point(232, 139));
 outputEdit.setFont(new Font("MS Sans Serif", 14.0f,
 FontSize.CHARACTERHEIGHT,
 FontWeight.BOLD,
 false, false, false));
 outputEdit.setLocation(new Point(40, 30));
 outputEdit.setSize(new Point(160, 23));
 outputEdit.setTabIndex(0);
 outputEdit.setText("");
 outputEdit.setReadOnly(true);
 inputComboBox.setFont(new Font("MS Sans Serif", 14.0f,
 FontSize.CHARACTERHEIGHT,
 FontWeight.BOLD,
 false, false, false));
 inputComboBox.setLocation(new Point(40, 90));
 inputComboBox.setSize(new Point(160, 24));
 inputComboBox.setTabIndex(1);
 inputComboBox.setText("");
 inputComboBox.setItems(new Object[] {
 "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"});
 inputComboBox.addOnSelectedIndexChanged(
 new EventHandler(this.inputComboBox_selectedIndexChanged));
 label1.setLocation(new Point(20, 10));
 label1.setSize(new Point(90, 20));
 label1.setTabIndex(2);
 label1.setTabStop(false);
 label1.setText("Month Selected:");
 label2.setLocation(new Point(20, 70));
 label2.setSize(new Point(100, 20));
 label2.setTabIndex(3);
 label2.setTabStop(false);
 label2.setText("Select a Month:");
 this.setNewControls(new Control[] {
 label2, label1, inputComboBox, outputEdit});
 }
 /**
 * The main entry point for the app. *
 * @param args Array of parameters passed to the app
 * via the command line.
 */
 public static void main(String args[])
 {
 app.run(new Form1());
 }
}


The inputComboBox_selectedIndexChanged() method is invoked whenever the user selects a new item from the combo box. The index to which the event refers is the index of the currently selected object. The first list item bears the index 0, the next index 1, and so forth.

The inputComboBox_selectedIndexChanged() method starts by querying the combo box as to what index the user just chose.

NOTE
At the moment the selectedIndexChanged() method is invoked, WFC has updated the index to reflect the newly selected value but has not updated the text. Calling inputComboBox.getSelectedText() or getText() at this point returns the text associated with the previous index value.

The inputComboBox_selectedIndexChanged() method then queries the combo box for a list of the objects it contains. The member of this list at the index position returned from getSelectedIndex() is the object the user has just chosen. ComboBox1 converts the selected Object into a String by calling the method toString(). Finally, the resulting string is copied into the outputEdit Edit control by calling the setText() method.

Why does WFC update the index but not the text?

It might seem curious that at the point the inputComboBox_selectedIndexChanged() method is called the index has been updated but the text has not. WFC does this to allow the inputComboBox_selectedIndexChanged() method to perform error checking before the user is possibly presented with incorrect output. For example, suppose that in the current program state, the month of February is not allowed because the user has already set the day to 30 or 31.

The following addition to the inputComboBox_selectedIndexChanged() method makes it impossible for the user to select February:

// if February is selected…
if (index == 1)
{
 // substitute January instead
 index = 0;
 inputComboBox.setIndex(index);
}


The call to setIndex() resets the index within the combo box. With this addition, whenever the user selects the month of February (index == 1) the function resets the selection to January (index == 0) instead. Once the inputComboBox_selectedIndexChanged() method is completed, the combo box displays the new text based upon the resulting value of the index. In this case, the month of January is displayed.

If WFC updated the text before calling inputComboBox_selectedIndexChanged(), the user would see an annoying flicker as February is displayed and then quickly replaced by January. By waiting to update the display until after the inputComboBox_selectedIndexChanged() method has completed, WFC is sure of the text that the program wants to display.

Populating the combo box with different object types

Just because strings are the most common combo box occupants doesn't mean that String is the only class you can store in a combo box. In fact, you can add any type of object that you want to a combo box as long as the type you select provides a toString() method that returns something the user will recognize. The presence of the toString() method is important because the value it returns gets displayed in the combo box list.

For example, the ComboBox2 example app below displays a combo box listing different tree types. When the user selects a tree type, the app displays the relative wood density and growth rate for that tree type in the output edit box previously used to display the selected month. The output from this app is shown in Figure 6-6.

Screenshot

Screenshot-6. The ComboBox2 app demonstrates that the objects added to a combo box list are not limited to strings.

The program listing for ComboBox2 is shown here (with the automatically generated initForm() method and dispose() method removed from the listing for brevity's sake).

import com.ms.wfc.app.*;
import com.ms.wfc.core.*;
import com.ms.wfc.ui.*;
import com.ms.wfc.html.*;
import java.lang.reflect.*;
/**
 * This class displays a static combo box containing TreeType
 * objects. This class also demonstrates the use of the Java
 * reflection features in displaying the name of the enumerated
 * types.
 */
public class Form1 extends Form
{
 public Form1()
 {
 // Required for Visual J Plus Plus Form Designer support
 initForm();
 // populate the list with a few different types of trees
 Object[] list = new Object[]
 {
 new TreeType("Maple", TreeHardness.SOFT, TreeGrowthRate.FAST),
 new TreeType("Oak", TreeHardness.HARD, TreeGrowthRate.SLOW),
 new TreeType("Birch", TreeHardness.SOFT, TreeGrowthRate.MEDIUM)
 };
 inputComboBox.setItems(list);
 }
 .
 .
 .
 /**
 * This method is invoked when the user selects a month
 * from the combo box.
 */
 private void inputComboBox_selectedIndexChanged(Object source,
 Event e)
 {
 // get the index of the selected object
 int index = inputComboBox.getSelectedIndex();
 // get a list of the items contained in the combo box
 Object[] list = inputComboBox.getItems();
 // index the object selected, and convert it into a string
 // String s = list[index].toString();
 TreeType t = (TreeType)list[index];
 String s = t.properties();
 // output the string in the output edit object
 outputEdit.setText(s); }
 /**
 * NOTE: The following code is required by the Visual J Plus Plus form
 * designer. It can be modified using the form editor. Do not
 * modify it using the code editor.
 */
 Container components = new Container();
 Edit outputEdit = new Edit();
 ComboBox inputComboBox = new ComboBox();
 Label label1 = new Label();
 Label label2 = new Label();
 private void initForm()
 {
 // …created by the Forms Designer…
 }
 /**
 * The main entry point for the app. * …
 */
 public static void main(String args[])
 {
 app.run(new Form1());
 }
}
/**
 * This class describes the relevant properties of a type of tree.
 */
class TreeType
{
 String name;
 int hardness;
 int growthRate;
 TreeType(String name, int hardness, int growthRate)
 {
 this.name = name;
 this.hardness = hardness;
 this.growthRate = growthRate;
 }
 public String toString()
 {
 return name;
 }
 public String properties()
 {
 Class c;
 Field f;
 // convert the hardness into a string:
 // use the reflection classes Class and Field
 c = TreeHardness.class; // get the class object
 f = c.getDeclaredFields()[hardness]; // get a list
 // of the constants
 String hl = f.toString(); // convert that into a string
 hl = hl.substring(hl.indexOf(".") + 1); // keep just the name
 // repeat the process for the growth rate
 c = TreeGrowthRate.class; f = c.getDeclaredFields()[growthRate];
 String gr = f.toString();
 gr = gr.substring(gr.indexOf(".") + 1);
 return hl + ", " + gr;
 }
}
class TreeHardness extends Enum
{
 public static final int SOFT = 0;
 public static final int MEDIUM = 1;
 public static final int HARD = 2;
 public static boolean valid(int value)
 {
 return (value >= SOFT) && (value <= HARD);
 }
}
class TreeGrowthRate extends Enum
{
 public static final int SLOW = 0;
 public static final int MEDIUM = 1;
 public static final int FAST = 2;
 public static boolean valid(int value)
 {
 return (value >= SLOW) && (value <= FAST);
 }
}


Once the form has been initialized by the initForm() method, the Form1 constructor adds three TreeType objects to the combo box list. You must program this code manually because it isn't possible to add user-defined types to a combo box using the Properties window.

The TreeType class contains nothing more than a tree type name, a wood hardness value, and a growth-rate value. The toString() method for TreeType returns the tree name.

The properties() method displays the hardness and growth rate as a string, but it does so in a very interesting way: properties() uses Java's reflection classes.

NOTE
The java.lang.reflection package allows a method to ask questions about another class.

First properties() fetches the Class object that describes TreeHardness. It then asks the class for a list of all its declared fields. The getDeclaredFields() method returns an array of Field objects.

NOTE
Fields is another word for data members. Declared fields are fields that are declared in the class. This does not include any fields inherited from a base class.

The enumerated class TreeHardness has only the members SOFT, MEDIUM, and HARD, so getDeclaredFields() returns an array containing three Field objects, one for each data member. (See the sidebar "What's an Enumeration Class?" for an explanation of enumerated classes.) Because we specified an index, [hardness], in our call to getDeclaredFields(), the method returns the field corresponding to the current hardness setting rather than returning the entire array.

What's an Enumeration Class?


You might be wondering why I bothered to create a TreeHardness and a TreeGrowthRate class just to hold a few public static final int definitions. There are several points about the TreeHardness and TreeGrowthRate classes worth noting.

  1. Each class extends Enum.
  2. Each class contains a list of public static final int constants.
  3. Each class contains the static method boolean valid(int). This method returns true if the integer passed it represents one of the valid values for that property. A class that fulfills these requirements is known as an enumeration type.
NOTE
There is no enum keyword in Java as there is in C++.

Defining your constants within enumeration types has several advantages. First, as you are defining the TreeType class, the Statement Completion feature will help you out. As soon as you type new TreeType(, Statement Completion gives you a list of arguments. The name of the argument should indicate the name of the class to use in describing it. Thus, the second argument to the TreeType constructor is called hardness, which prompts me to use the TreeHardness class. Once I have typed TreeHardness., Statement Completion drops down a list of possible values including SOFT, MEDIUM, and HARD (along with some methods and properties inherited from the base class).

NOTE
It's a standard Java coding convention to use all capital letters in the names of members of type public static final int.
NOTE
The public static final int data member takes the place of a const data member in C++.

The ComboBox2 example app uses the same reflection mechanism to display the available fields that the smart editor uses. Later we'll see that when we define our own controls the Toolbox has access to the enumerated properties of the object using this same reflection mechanism.

The call to f.toString() returns the name of the field as a string—SOFT, MEDIUM, or HARD. Unfortunately for us, the name that f.toString() returns includes the name of the class. I added a call to substring() on the next line to strip off the class name by removing everything prior to the "." that normally separates the class name from the member's name.

The properties() method repeats the process for the growth rate before tacking the two property values together and returning the result as a string.

The inputComboBox_selectedIndexChanged() method that handles the combo box selection is the same here as in the earlier ComboBox1 example, except that it calls the TreeType.properties() method rather than the toString() method. (With the toString() call left in place, the program would still work, but only the name of the tree type and not its properties would appear in the output window)

Populating the combo box list dynamically

At the time you're writing a program, it isn't always possible to know what is to go into a combo box. Sometimes a combo box list must be calculated when the program runs. For example, the following ComboBox3 code populates a combo box with the names of the files in the current directory.

import com.ms.wfc.app.*;
import com.ms.wfc.core.*;
import com.ms.wfc.ui.*;
import com.ms.wfc.html.*;
import com.ms.wfc.io.*;
/**
 * This class displays a combo box whose contents are
 * calculated at run time from the list of files in the
 * current directory.
 */
public class Form1 extends Form
{
 public Form1()
 {
 // Required for Visual J Plus Plus Form Designer support
 initForm();
 // populate the combo box with a list of all file names
 // in the current directory
 inputComboBox.setItems(File.getFiles("*.*"));
 }
 // …rest of the program is same as ComboBox1…


The public static method File.getFiles() returns a list of all of the files and directories that match the filter passed to it. The File.getFiles() method returns this list as an array of String objects containing the names of the files. This array is ideal, since this is exactly what we need to pass to the setItems() method. Setting the sorted property of the ComboBox object to true in the Properties window ensures that the files' names appear in alphabetical order in the combo box list. The output of executing this program in the test directory is shown in Figure 6-7.

Screenshot

Screenshot-7. The contents of the combo box must often be calculated at run time, as in this file list.

ListBox Control

As powerful as it is, the combo box has one serious limitation: only one member from the drop-down list can be selected at a time. The ListBox control doesn't suffer from this limitation. Visually the list box is rather different from the combo box. A list box has no drop-down list; its list remains visible at all times. Fortunately, you can set the size of the list box in the Forms Designer. If there are more objects in the list box than can be displayed at one time, the list box automatically displays a vertical scroll bar. In addition, the list box has no output window where the currently selected item is displayed; instead, selected items are displayed in inverse video.

The ListBox control has three modes of operation: One (also known as Single Select) mode, Multi Simple mode, and Multi Extended mode. In Single Select mode, only one item can be selected at a time. In Multi Simple mode, the user clicks on an item to select it. Clicking the same item again deselects it. The user can select any number of items but must select each item individually.

Multi Extended mode is the same mode used in Windows Explorer and most other programs that use a list box. With this type of list box the user can select large blocks of items. Clicking one item and then clicking a second while holding down the Shift key automatically selects the two items and all of the items in between. In addition, the user can select or deselect individual items by holding down the Control key. (In effect, as long as the user presses the Control key, the list box operates in Multi Simple mode.)

From the programmer's point of view, the list box is very similar to the check box. In Single Select mode, the list box works the same as the combo box: calling getSelectedIndex() from within the event handler returns the index of the selected item.

Usually it's preferable to operate list boxes in Multi Simple or Multi Extended mode. In either of these modes, the event handler must be prepared to read a list of selected items, as shown in the following code.

private void selectListBox_selectedIndexChanged(Object source, Event e)
{
 Object[] list = selectListBox.getSelectedItems();
 // …operate on the list…
}


Using this approach might mean entering a for loop to iterate through the items in the list.

Multiselect list boxes

The following example, ListBoxDemo, is a file deletion utility that uses the ListBox control to allow the user to select the files to delete. (ListBoxDemo does not actually delete the files selected.) After creating a Windows-based project in the normal way, I used the Forms Designer to create the display shown in Figure 6-8.

Screenshot

Screenshot-8. The ListBoxDemo program demonstrating how the list box can enable the selection of multiple items.

The source code for the ListBoxDemo app is as follows. (I left out sections of code, such as those created by the Forms Designer, for brevity).

import com.ms.wfc.app.*;
import com.ms.wfc.core.*;
import com.ms.wfc.ui.*;
import com.ms.wfc.html.*;
import com.ms.wfc.io.*;
/**
 * This ListBoxDemo uses a list box to select files to
 * "delete." (Uncomment the call to File.delete() if
 * you want the app to actually delete anything.)
 */
public class Form1 extends Form
{
 String currentPath = ".";
 public Form1()
 {
 // Required for Visual J Plus Plus Form Designer support
 initForm();
 updateSelectListBox("*.*");
 }
 .
 .
 .
 private void filterEdit_textChanged(Object source, Event e)
 {
 String s = filterEdit.getText();
 currentPath = File.getDirectory(s);
 updateSelectListBox(s); }
 private void updateSelectListBox(String filter)
 {
 // get a list of the files; ignore any errors
 // that might arise if the directory doesn't exist
 Object[] list = new Object[0];
 try
 {
 list = File.getFiles(filter);
 }
 catch(Exception e)
 {
 }
 // update the select list box with the file names
 selectListBox.setItems(list);
 // update the select list box
 selectListBox_selectedIndexChanged(null, null);
 }
 private void selectListBox_selectedIndexChanged(
 Object source, Event e)
 {
 Object[] list = selectListBox.getSelectedItems();
 String[] fileArray = new String[list.length];
 for (int i = 0; i < list.length; i++)
 {
 // if the files selected are not in the
 // default directory, convert the filename
 // into the full path
 String filePath = currentPath
 + "\\"
 + (String)list[i];
 // if the file is a directory…
 if (File.isDirectory(filePath))
 {
 // put an asterisk on the front
 filePath = "*" + filePath;
 }
 else
 {
 filePath = " " + filePath;
 }
 fileArray[i] = filePath;
 }
 outputEdit.setLines(fileArray);
 }
 private void deleteButton_click(Object source, Event e)
 {
 String[] list = outputEdit.getLines();
 for(int i = 0; i < list.length; i++)
 {
 // the following function deletes the file
 // without putting it into the Recycle Bin
 // File.delete(list[i]);
 }
 filterEdit_textChanged(null, null);
 }
 /**
 * NOTE: The following code is required by the Visual J Plus Plus form
 * designer. It can be modified using the form editor. Do not
 * modify it using the code editor.
 */
 Container components = new Container();
 Edit outputEdit = new Edit();
 ListBox selectListBox = new ListBox();
 Button deleteButton = new Button();
 Edit filterEdit = new Edit();
 Label label1 = new Label();
 Label label2 = new Label();
 Label label3 = new Label();
 private void initForm()
 {
 // …built automatically by Forms Designer…
 }
 /**
 * The main entry point for the app. * …
 */
 public static void main(String args[])
 {
 app.run(new Form1());
 }
}


Once initForm() has created the form with the associated ListBox control, the Form1() constructor calls the updateSelectListBox() method to populate the list box with a list of file names. Passing the filter *.* instructs updateSelectListBox() to select all files in the current directory. The updateSelectListBox() method gets a list of the files that pass the current filter by calling File.getFiles(). By catching any exception thrown, updateSelectListBox() ignores any errors that getFiles() might detect. (For example, getFiles() throws an exception if the directory specified doesn't exist.) The resulting list, which might be empty, is passed to the selectListBox object for display.

As the user selects items from the file selection list box, WFC automatically invokes selectListBox_selectedIndexChanged(). This function gets a list of the selected items by calling getSelectedItems(). Knowing that these are the names of files, selectListBox_selectedIndexChanged() iterates through the list adding the full path to each file. In addition, if a file is a directory the function adds an asterisk to the front of the file name. The resulting list of file names is displayed in the outputEdit object by calling setLines().

The deleteButton_click() method is invoked when the user chooses the Delete button. This function gets a list of the files to be deleted from the outputEdit object by calling getLines(). It then loops through this list "deleting" each file.

NOTE
Since deleting files is too dangerous in a simple demo for a tutorial, I've commented out the call to File.delete(). If you would like this utility to actually delete the selected files, uncomment the call to File.delete() and recompile the program. However, be advised that File.delete() deletes each file or directory immediately and does not move the file to the Recycle Bin.
Comments