Suppose you are the software developer for your own software business specializing in computer games. You want to develop an order form that customers can use to order software over the Web. At the moment you have three software titles—a chess game, a checkers game, and a crossword puzzle game. The assumption is that the user will choose one or more of these titles from some kind of menu. The user must also indicate a payment option—either E-cash, credit card, or debit card. These options are mutually exclusive—the user can choose one and only one.

Let's design a GUI for this program. Unlike the previous problem where the input was a numeric value, in this problem the input will be the user's selection from some kind of menu. The result will be the creation of an order. Let's suppose that this part of the task happens behind the scenes—that is, we don't have to worry about creating an actual order. The output the user sees will simply be an acknowledgment that the order was successfully submitted.

There are several kinds of controls needed for this interface. First, a conventional way to have users indicate their purchase decisions is to have them click a Submit button. They should also have the option to cancel the transaction at any time.

In addition to these button controls, a couple of menus must be presented, one for the software titles, and one for the payment choices. Swing and AWT libraries provide many options for building menus.

One key requirement for this interface is the mutually exclusive payment options. A conventional way to handle this kind of selection is with a JRadioButton—a button that belongs to a group of mutually exclusive alternatives. Only one button from the group may be selected at one time. The selection of software titles could be handled by a collection of checkboxes. A JCheckbox is a button that can be selected and deselected and that always displays its current state to the user. Using a checkbox will make it obvious to the user exactly what software has been selected.

To complete the design, let's use a JTextArea again to serve as something of a printed order form. It will confirm the user's order and display other messages needed during the transaction.

Given these decisions, we arrive at the design shown in Figure 13.7.1.

In this case, our design uses a JPanel as the main container, instead of using the top window itself. The reason for this decision is that we want to use Swing Borders around the various JPanels to enhance the overall visual appeal of the design. The borders will have titles that help explain the purpose of the various panels.

For the top-level window, we are using a border layout. For the main JPanel we are using a $$3 \times 1$$ GridLayout. The components in the main panel are the JTextArea and two other JPanel s. The GridLayout will take care of sizing these so they are all of equal size.

The center panel, which uses a flow layout, contains panels for the checkboxes and the radio buttons. These elements are grouped within their own panels. Again, we can put a border around them in the final implementation (Fig. 13.26). The button panels use a BoxLayout, which we will discuss later. This design leads to the most complex containment hierarchy thus far.

Because we will need three checkboxes, one for each title, and three radio buttons, one for each payment option, it will be useful again to use arrays to store both the buttons and their titles:

private ButtonGroup optGroup = new ButtonGroup();
private JCheckBox titles[] = new JCheckBox[NTITLES];
private String titleLabels[] =
{"Chess Master - $59.95", "Checkers Pro -$9.95",
"Crossword Maker - \$19.95"};
private String optionLabels[] = {"Credit Card",
"Debit Card", "E-cash"};


Again, the advantage of this design is that it simplifies the instantiation and initialization of the buttons:

for(int k = 0; k < titles.length; k++) {
titles[k] = new JCheckBox(titleLabels[k]);
}


The only difference between this array of checkboxes and the keypad array of buttons that we used in the Converter program is that checkboxes generate ItemEvents instead ActionEvents. Therefore, each checkbox must be registered with an ItemListener (and, of course, the app itself must implement the ItemListener interface). We'll show how ItemEvent is handled later.

The code for instantiating and initializing the radio buttons is almost the same:

for(int k = 0; k < options.length; k++) {
}
options[0].setSelected(true); // Set first button 'on'


Radio buttons also generate ItemEvent s, so they too must be registered with an ItemListener. Note that the first button is set on, which represents a default payment option for the user.

The difference between checkboxes and radio buttons is that radio buttons must be added to a ButtonGroup—here named optGroup—in order to enforce mutual exclusion among them. A ButtonGroup is an object whose sole task is to enforce mutual exclusion among its members. Whenever you click one radio button, the ButtonGroup will automatically be notified of this event and will turn off whatever other button was turned on. As Figure 13.7.2 illustrates, radio buttons are monitored by two different objects, a ButtonGroup, which manages the radio buttons' states, and an ItemListener, which listens for clicks on the buttons and takes appropriate actions.

Activity13.7.1.

Try the Checkbox Choices app below. Fork the repl and change the code in Choices.java to add the RadioButtons using the code above.

Note the effective division of labor in the design of the various objects to which a radio button belongs. The optionPanel is a GUI component (a JPanel) that contains the button within the visual interface. Its role is to help manage the graphical aspects of the button's behavior. The ButtonGroup is just an Object, not a GUI component. Its task is to monitor the button's relationship to the other buttons in the group. Each object has a clearly delineated task.

This division of labor is a key feature of object-oriented design. It is clearly preferable to giving one object broad responsibilities. For example, a less effective design might have given the task of managing a group of buttons to the JPanel that contains them. However, this would lead to all kinds of problems, not least of which is the fact that not everything in the container belongs to the same button group. So a clear division of labor is a much preferable design.

Subsection13.7.2Swing Borders

The Swing Border and BorderFactory classes can place borders around virtually any GUI element. Using borders is an effective way to make the grouping of components more apparent. Borders can have titles, which enhance the GUI's ability to guide and inform the user. They can also have a wide range of styles and colors, thereby helping to improve the GUI's overall appearance.

A border occupies some space around the edge of a JComponent. For the Acme Software Titles interface, we place titled borders around four of the panels (Figure 13.7.4). The border on the main panel serves to identify the company again. The one around the button panel serves to group the two control buttons. The borders around both the checkbox and the radio button menus help to set them apart from other elements of the display and help identify the purpose of the buttons.

Attaching a titled border to a component—in this case to a JPanel—is very simple. It takes one statement:

choicePanel.setBorder(
BorderFactory.createTitledBorder("Titles"));


The setBorder() method is defined in JComponent, is inherited by all Swing components, and takes a Border argument. In this case, we use the BorderFactory class to create a border and assign it a title. There are several versions of the static createTitledBorder() method. This version lets us specify the border's title. It uses default values for type of border (etched), the title's position (sitting on the top line), justification (left), and for font's type and color.

As you would expect, the Border and BorderFactory classes contain methods that let you exert significant control over the border's look and feel. You can even design and create your own custom borders.

Subsection13.7.3The BoxLayout Manager

Another type of layout to use is the BoxLayout. This can be associated with any container, and it comes as the default with the Swing Box container. We use it in this example to arrange the checkboxes and radio buttons ( Figure 13.7.1).

A BoxLayout is like a one-dimensional grid layout. It allows multiple components to be arranged either vertically or horizontally in a row. The layout will not wrap around, as does the FlowLayout. Unlike the GridLayout, the BoxLayout does not force all its components to be the same size. Instead, it tries to use each component's preferred width (or height) in arranging them horizontally (or vertically). (Every Swing component has a preferred size that is used by the various layout managers in determining the component's actual size in the interface.) The BoxLayout manager also tries to align its components' heights (for horizontal layouts) or widths (for vertical layouts).

Once again, to set the layout manager for a container you use the setLayout() method:

choicePanel.setLayout(new
BoxLayout(choicePanel,BoxLayout.Y_AXIS));


The BoxLayout() constructor has two parameters. The first is a reference to the container that's being managed, and the second is a constant that determines whether horizontal (x-axis) or vertical (y-axis) alignment is used.

One nice feature of the BoxLayout is that it can be used in combinations to imitate the look of the very complicated GridBoxLayout. For example, Figure 13.7.5 shows an example with two panels (Panel1 and Panel2) arranged horizontally within an outer box (Panel0), each containing four components arranged vertically. The three panels all use the BoxLayout.

Subsection13.7.4The ItemListener Interface

In this section, we will describe how to handle menu selections. Whenever the user makes a menu selection, or clicks a check box or radio button, an ItemEvent is generated. ItemEvents are associated with items that make up menus, including JPopupMenu s, JCheckboxes, JRadioButton s, and other types of menus. Item events are handled by the ItemListener interface, which consists of a single method, the itemStateChanged() method:

public void itemStateChanged(ItemEvent e) {
display.setText("Your order so far (Payment by: ");
for (int k = 0; k < options.length; k++ )
if (options[k].isSelected())
display.append(options[k].getText() + ")\n");
for (int k = 0; k < titles.length; k++ )
if (titles[k].isSelected())
display.append("\t" + titles[k].getText() + "\n");
} // itemStateChanged()


This version of the method handles item changes for both the checkbox menu and the radio buttons menu. The code uses two consecutive for loops. The first iterates through the options menu (radio buttons) to determine what payment option the user has selected. Since only one option can be selected, only one title will be appended to the display. The second loop iterates through the titles menu (checkboxes) and appends each title the user selected to the display. This way the complete status of the user's order is displayed after every selection. The isSelected() method is used to determine if a checkbox or radio button is selected or not.

In this example, we have no real need to identify the item that caused the event. No matter what item the user selected, we want to display the entire state of the order. However, like the ActionEvent class, the ItemEvent class contains methods that can retrieve the item that caused the event:

getItem();  // Returns a menu item within a menu


The getItem() method is the ItemListener's analogue to the ActionEvent's getSource() method. It enables you to obtain the object that generated the event but returns a representation of the item that was selected or deselected.

Activity13.7.2.

Fork the Checkbox Choices app below or use the repl that you forked in the previous activity. Change the code in Choices.java to add extends ItemListener to the class and to add an itemStateChanged() method for the checkboxes using the code above.

Subsection13.7.5The OrderApp

The design of the OrderApp is summarized in Figure 13.7.6 and its complete implementation is given in Listing 13.7.7. Try it in the activity below.

Activity13.7.3.

Try the OrderApp below.

There are several important points to make about this program. First, five JPanels are used to organize the components into logical and visual groupings. This conforms to the design shown in Figure 13.7.6.

Second, note the use of titled borders around the four internal panels. These help reinforce that the components within the border are related by function.

The constructor is used to initialize the interface. This involves setting the layouts for the various containers and filling the containers with their components. Because their initializations are relatively long, the checkboxes and radio buttons are initialized in separate methods, the initChoices() and initOptions() methods, respectively.

Finally, note how the actionPerformed() method creates a mock order form in the display area. This allows the user to review the order before it is submitted. Also note that the algorithm used for submittal requires the user to confirm an order before it is actually submitted. The first time the user clicks the Submit button, the button's label is changed to, “Confirm Order,” and the user is prompted in the display area to click the Confirm button to submit the order. This design allows the interface to catch inadvertent button clicks.

A user interface should anticipate errors by the user. When a program involves an action that can't be undone—such as placing an order—the program should make sure the user really wants to take the action before carrying it out.

Subsection13.7.6Self-Study Exercise

Exercise13.7.9.FlowLayout.

What's your favorite interface horror story? How would you have remedied the problem? The interface needn't be a computer interface.

Exercise13.7.10.

Using the OrderApp as a guide, create a GUI for a form or survey with checkboxes and radio buttons.