Hour 20

Responding to User Events

The graphical user interface that you developed during the past hour can run on its own without any changes. Buttons can be clicked, text fields filled with text, and the applet window can be resized with wild abandon. Sooner or later, however, even the least discriminating user is going to be left wanting more. The graphical user interface that a program offers has to cause things to happen when a mouse click or keyboard entry occurs. Text areas and other components must be updated to show what's happening as the program runs.

These things are possible when your Java program can respond to user events. An event is something that happens when a program runs, and user events are things that a user causes by using the mouse, keyboard, or another input device. Responding to user events often is called event-handling, and it's the activity you'll be learning about during this hour.

The following topics will be covered:

Getting Your Programs to Listen

Responding to user events in a Java program requires the use of one or more EventListener interfaces. As you might recall from using the Runnable interface for multithreaded programs, interfaces are special classes that enable a class of objects to inherit behavior that it would not be able to use otherwise. Adding an EventListener interface involves two things right away. First, because the listening classes are part of the java.awt.event group of classes, you must make them available with the following statement:

import java.awt.event.*;

Secondly, the class must use the implements statement to declare that it will be using one or more listening interfaces. The following statement creates a class that uses ActionListener, an interface used with buttons and other components:

public class Graph extends java.applet.Applet implements ActionListener {

The EventListener interfaces enable a component of a graphical user interface to generate user events. Without one of the listeners in place, a component cannot do anything that can be heard by other parts of a program. A program must include a listener interface for each type of component it wants to listen to. To have the program respond to a mouse click on a button or the Enter key being pressed in a text field, you must include the ActionListener interface. To respond to the use of a choice list or check boxes, the ItemListener interface is needed. When you require more than one interface, separate their names with commas after the implements statement. The following is an example:

public class Graph3D extends java.applet.Applet implements ActionListener, MouseListener {

Setting Up Components to Be Heard

Once you have implemented the interface that is needed for a particular component, you have to set up that component so that it generates user events. A good example to consider is the use of Button objects as components. When you use a button in an interface, something has to happen in response to the click of the button. Otherwise, the button's not needed for the interface at all.

The program that needs to respond to the button click should use the ActionListener interface. The name of the interface comes from calling a button click or the press of the Enter key an action event, because it signifies that some kind of action should be taken. To make a Button object generate an event, use the addActionListener() method, as in the following:

Button fireTorpedoes = new Button("Fire torpedoes");
fireTorpedoes.addActionListener(this);

This code creates the fireTorpedoes object and then calls that object's addActionListener() method. The this statement indicates that the current class of objects will receive the user event and handle it as needed.

Handling User Events

When a user event is generated by a component that has a listener, a method will be called automatically. The method must be found in the class that was specified when the listener was attached to the component. For instance, in the example of the fireTorpedoes object, the method must be located in the same program because the this statement was used.

Each listener has different methods that are called to receive their events. The ActionListener interface sends events to a class called actionPerformed(). The following is a short example of an actionPerformed() method:

void public actionPerformed(ActionEvent evt) {
    // method goes here
}

All action events sent in the program will go to this method. If only one component in a program can possibly send action events, you can put statements in this method to handle the event. If more than one component can send these events, you need to use the object that is sent to the method.

In this case, an ActionEvent object is sent to the actionPerformed() method. There are several different classes of objects that represent the user events that can be sent in a program. These classes have methods you can use to determine which component caused the event to happen. In the actionPerformed() method, if the ActionEvent object is named evt, you can identify the component with the following statement:

String cmd = evt.getActionCommand();

The getActionCommand() method sends back a string. If the component is a button, the string will be the label that is on the button. If it's a text field, the string will be the text entered in the field. The getSource() method sends back the object that caused the event.

You could use the following actionPerformed() method to receive events from three components: a Button object called start, a TextField called speed, and another TextField called viscosity:

void public actionPerformed(ActionEvent evt) {
    Object source = evt.getSource();
    if (source == speed) {
        // speed field caused event
    } else if (source == viscosity) {
        // viscosity caused event
    } else
        // start caused event

You can use the getSource() method with all types of user events to identify the specific object that caused the event.

Check Box and Choice Events

Choice lists and check boxes require the ItemListener interface. To make one of these components generate events, use the addItemListener() method. For example, the following statements create a check box called superSize and cause it to send out user events when selected or deselected:

Checkbox superSize = new Checkbox("Super Size", true);
superSize.addItemListener(this);

These events are received by the itemStateChanged() method, which takes an ItemEvent object as an argument. To see which object caused the event, you can use the getItem() method.

To determine whether a check box is selected or deselected, use the getStateChange() method with the constants ItemEvent.SELECTED and ItemEvent.DESELECTED. The following is an example for an ItemEvent object called item:

int status = item.getStateChange();
if (status == ItemEvent.SELECTED)
    // item was selected

To determine the value that has been selected in a Choice object, use getItem() and convert that value to a string, as in the following:

Object which = item.getItem();
String answer = (String) which;

Other Text Field Events

If you want to check on a text field or text area after its value has been changed, use the TextListener interface. The addTextListener() method makes a text component actively generate user events, and the textValueChanged() method receives the events. The following example receives events from TextField objects called address and zipCode:

public void textValueChanged(TextEvent txt) {
    Object source = txt.getSource();
    if (source == address)
        String newAddress = address.getText();
    } else
        String newZipCode = zipCode.getText();
}

The getText() method is used with text components to retrieve their values. The setText() method can set the value of a text field or text area.

Enabling and Disabling Components

You may have seen a component in a program that appears shaded instead of its normal appearance. This shading indicates that users cannot do anything to the component because it is disabled. Disabling and enabling components as a program runs is done with the setEnabled() method of the component. A Boolean value is sent as an argument to the method, so setEnabled(true) enables a component for use, and setEnabled(false) disables a component.

This method is an effective way to prevent a component from sending a user event when it shouldn't. For example, if you're writing a Java applet that takes a user's address in text fields, you might want to disable a Continue button until all of the fields have some kind of value.

Workshop: A Little Lotto Madness

For more examples of how event-handling works in the context of a Java program, you will finish the Lotto applet that you began during Hour 19, "Building a Simple User Interface." The name of the applet will be changed from LottoGUI to LottoMadness to reflect its status as a program. The purpose of this applet is to assess the user's chances of winning a six-number Lotto drawing in a lifetime. Figure 20.1 shows a screen capture of the program as it continues to run.

Instead of using probability to figure out this problem, the computer will take a more anecdotal approach: It will conduct drawing after drawing after drawing until you win. Because the 6-out-of-6 win is extremely unlikely, the program also will report on any combination of three, four, or five winning numbers.

The interface that you created includes 12 text fields for Lotto numbers and two check boxes labeled Quick Pick and Personal. Six of the text fields are disabled from input; they will be used to display the winning numbers of each drawing. The other six text fields are for the user's choice of numbers. If the user wants to select six numbers manually, he should select the Personal check box. If he selects the Quick Pick box instead, six random numbers will appear in the text fields.

Figure 20.1. The LottoMadness applet continues to run.

Three buttons control the activity of the program: Stop, Play, and Reset. When the Play button is pressed, the program starts a thread called playing and generates Lotto drawings as fast as it can. Pressing the Stop button stops the thread, and pressing Reset clears all fields so the user can start the number-crunching all over again.

The LottoMadness applet implements three interfaces: ActionListener, ItemListener, and Runnable. The first two are needed to listen to user events generated by the buttons and check boxes on the applet. The program does not need to listen to any events related to the text fields, because they will be used strictly to store the user's choice of numbers. The user interface handles this function automatically.

Listing 20.1 shows the full text of the LottoMadness applet. The shaded lines in the listing were unchanged from LottoGUI.java. The unshaded statements are what must be added to respond to user events and run the Lotto drawings.

Making this program aware of user events for some components requires only a few additions. The class statement in Lines 4-6 is changed to use the interfaces. Lines 46-51 add the listeners that are needed for the two check boxes and three button components. One line is added to the program in the init() method where components are placed on the user interface. In Line 77, the stop object is disabled with the setEnabled(false) method. Because no drawings are taking place when the program begins running, the Stop button should not be usable at that point.

The following methods are used in the program to accomplish specific tasks:

The actionPerformed() method of the applet receives the action events caused when the user presses Stop, Play, or Reset. The getActionCommand() method retrieves the label of the button, which is used to determine which component was pressed. Pressing the Play button causes four components to be disabled so that they do not interfere with the drawings as they are taking place. Pressing Stop reverses this by enabling every component except for the Stop button.

The itemStateChanged()method receives the user events caused when one of the check boxes is selected. The getItem() method sends back an Object, which is converted to a string to determine the label of the check box.

One last thing to note about the LottoMadness applet is the lack of variables used to keep track of things like the number of drawings, winning counts, and Lotto number text fields. This element of user interface programming differs from other types of programs. You can use the interface to store values and display them automatically.

Load the original LottoGUI.java program into your word processor and save the file under the new name LottoMadness.java. After changing the class statement to reflect the new name of the program and the interfaces it will use, insert all of the non-shaded lines from Listing 20.1 and save the file.

Listing 20.1. The full text of LottoMadness.java.


  1: import java.awt.*;
  2: import java.awt.event.*;
  3:
  4: public class LottoMadness extends java.applet.Applet
  5:     implements ItemListener, ActionListener,
  6:     Runnable {
  7:
  8:     Thread playing;
  9:
 10:     // set up row 1
 11:     Panel row1 = new Panel();
 12:     CheckboxGroup option = new CheckboxGroup();
 13:     Checkbox quickpick = new Checkbox("Quick Pick", option, false);
 14:     Checkbox personal = new Checkbox("Personal",option, true);
 15:     // set up row 2
 16:     Panel row2 = new Panel();
 17:     Label numbersLabel = new Label("Your picks: ", Label.RIGHT);
 18:     TextField[] numbers = new TextField[6];
 19:     Label winnersLabel = new Label("Winners: ", Label.RIGHT);
 20:     TextField[] winners = new TextField[6];
 21:     // set up row 3
 22:     Panel row3 = new Panel();
 23:     Button stop = new Button("Stop");
 24:     Button play = new Button("Play");
 25:     Button reset = new Button("Reset");
 26:     // set up row 4
 27:     Panel row4 = new Panel();
 28:     Label got3Label = new Label("3 of 6: ", Label.RIGHT);
 29:     TextField got3 = new TextField();
 30:     Label got4Label = new Label("4 of 6: ", Label.RIGHT);
 31:     TextField got4 = new TextField();
 32:     Label got5Label = new Label("5 of 6: ", Label.RIGHT);
 33:     TextField got5 = new TextField();
 34:     Label got6Label = new Label("6 of 6: ", Label.RIGHT);
 35:     TextField got6 = new TextField(10);
 36:     Label drawingsLabel = new Label("Drawings: ", Label.RIGHT);
 37:     TextField drawings = new TextField();
 38:     Label yearsLabel = new Label("Years: ", Label.RIGHT);
 39:     TextField years = new TextField();
 40:
 41:     public void init() {
 42:         setBackground(Color.lightGray);
 43:         GridLayout appletLayout = new GridLayout(5, 1, 10, 10);
 44:         setLayout(appletLayout);
 45:
 46:         // Add listeners
 47:         quickpick.addItemListener(this);
 48:         personal.addItemListener(this);
 49:         stop.addActionListener(this);
 50:         play.addActionListener(this);
 51:         reset.addActionListener(this);
 52:
 53:         FlowLayout layout1 = new FlowLayout(FlowLayout.CENTER, 10, 10);
 54:         row1.setLayout(layout1);
 55:         row1.add(quickpick);
 56:         row1.add(personal);
 57:         add(row1);
 58:
 59:         GridLayout layout2 = new GridLayout(2, 7, 10, 10);
 60:         row2.setLayout(layout2);
 61:         row2.setLayout(layout2);
 62:         row2.add(numbersLabel);
 63:         for (int i = 0; i < 6; i++) {
 64:             numbers[i] = new TextField();
 65:             row2.add(numbers[i]);
 66:         }
 67:         row2.add(winnersLabel);
 68:         for (int i = 0; i < 6; i++) {
 69:             winners[i] = new TextField();
 70:             winners[i].setEditable(false);
 71:             row2.add(winners[i]);
 72:         }
 73:         add(row2);
 74:
 75:         FlowLayout layout3 = new FlowLayout(FlowLayout.CENTER, 10, 10);
 76:         row3.setLayout(layout3);
 77:         stop.setEnabled(false);
 78:         row3.add(stop);
 79:         row3.add(play);
 80:         row3.add(reset);
 81:         add(row3);
 82:
 83:         GridLayout layout4 = new GridLayout(2, 3, 20, 10);
 84:         row4.setLayout(layout4);
 85:         row4.add(got3Label);
 86:         got3.setEditable(false);
 87:         row4.add(got3);
 88:         row4.add(got4Label);
 89:         got4.setEditable(false);
 90:         row4.add(got4);
 91:         row4.add(got5Label);
 92:         got5.setEditable(false);
 93:         row4.add(got5);
 94:         row4.add(got6Label);
 95:         got6.setEditable(false);
 96:         row4.add(got6);
 97:         row4.add(drawingsLabel);
 98:         drawings.setEditable(false);
 99:         row4.add(drawings);
100:         row4.add(yearsLabel);
101:         years.setEditable(false);
102:         row4.add(years);
103:         add(row4);
104:     }
105:
106:     public void actionPerformed(ActionEvent event) {
107:         String command = event.getActionCommand();
108:         if (command == "Reset")
109:             clearAllFields();
110:         if (command == "Play") {
111:             playing = new Thread(this);
112:             playing.start();
113:             play.setEnabled(false);
114:             stop.setEnabled(true);
115:             reset.setEnabled(false);
116:             quickpick.setEnabled(false);
117:             personal.setEnabled(false);
118:         }
119:         if (command == "Stop") {
120:             playing.stop();
121:             stop.setEnabled(false);
122:             play.setEnabled(true);
123:             reset.setEnabled(true);
124:             quickpick.setEnabled(true);
125:             personal.setEnabled(true);
126:         }
127:
128:     }
129:
130:     public void itemStateChanged(ItemEvent event) {
131:         String command = (String) event.getItem();
132:         if (command == "Quick Pick") {
133:             for (int i = 0; i < 6; i++) {
134:                 int pick;
135:                 do {
136:                     pick = (int)Math.floor(Math.random() * 50 + 1);
137:                 } while (numberGone(pick, numbers, i));
138:                 numbers[i].setText("" + pick);
139:             }
140:         } else {
141:             for (int i = 0; i < 6; i++)
142:                 numbers[i].setText(null);
143:         }
144:     }
145:
146:     void clearAllFields() {
147:         for (int i = 0; i < 6; i++) {
148:             numbers[i].setText(null);
149:             winners[i].setText(null);
150:         }
151:         got3.setText(null);
152:         got4.setText(null);
153:         got5.setText(null);
154:         got6.setText(null);
155:         drawings.setText(null);
156:         years.setText(null);
157:     }
158:
159:     void addOneToField(TextField field) {
160:         int num = Integer.parseInt("0" + field.getText());
161:         num++;
162:         field.setText("" + num);
163:     }
164:
165:     boolean numberGone(int num, TextField[] pastNums, int count) {
166:         for (int i = 0; i < count; i++)
167:             if (Integer.parseInt(pastNums[i].getText()) == num)
168:                 return true;
169:         return false;
170:     }
171:
172:     boolean matchedOne(TextField win, TextField[] allPicks) {
173:         for (int i = 0; i < 6; i++) {
174:             String winText = win.getText();
175:             if ( winText.equals( allPicks[i].getText() ) )
176:                 return true;
177:         }
178:         return false;
179:     }
180:
181:     public void run() {
182:         while (true) {
183:             addOneToField(drawings);
184:             int draw = Integer.parseInt(drawings.getText());
185:             float numYears = (float)draw / 104;
186:             years.setText("" + numYears);
187:
188:             int matches = 0;
189:             for (int i = 0; i < 6; i++) {
190:                 int ball;
191:                 do {
192:                     ball = (int)Math.floor(Math.random() * 50 + 1);
193:                 } while (numberGone(ball, winners, i));
194:                 winners[i].setText("" + ball);
195:                 if (matchedOne(winners[i], numbers))
196:                     matches++;
197:             }
198:             switch (matches) {
199:                 case 3:
200:                     addOneToField(got3);
201:                     break;
202:                 case 4:
203:                     addOneToField(got4);
204:                     break;
205:                 case 5:
206:                     addOneToField(got5);
207:                     break;
208:                 case 6:
209:                     addOneToField(got6);
210:                     stop.setEnabled(false);
211:                     play.setEnabled(true);
212:                     playing.stop();
213:             }
215:         }
216:     }
218: } 


After saving the LottoMadness.java file, load the file LottoGUI.html into your word processor and make one change--the text LottoGUI.class should be LottoMadness.class. Save it under the new name LottoMadness.html. Compile the LottoMadness applet with the javac compiler tool and then try out the applet by loading its Web page into the appletviewer.

Summary

Using the Abstract Windowing Toolkit and Java's event-handling features, you can create a professional-looking program with a modest amount of programming. Although the LottoMadness applet is longer than many of the examples you have worked on during the last 20 hours, half of the program was comprised of statements to build the interface.

If you spend some time running the LottoMadness applet, you will become even more bitter and envious about the good fortune of the people who win these six-number lottery drawings. The run of the program shown in Figure 20.1 indicates that you could blow 27 grand and the best 266 years of your life buying tickets, only to win a handful of 4-of-6 and 3-of-6 prizes. In comparison to those odds, the chance to make Java programming skills pay off almost seems like a sure thing.

Q&A

Q Is there a way to use different colors in an interface?

A
You can use Color objects to change the appearance of each component in several ways. The setBackground() method designates the background elements, and setForeground() sets foreground elements. You must use these methods with the components themselves. The setBackground() method of the applet will not change the color of containers and components within the applet.

Q Do you need to do anything with the paint() or repaint() method to indicate that a text field has been changed?

A
After the setText() method of a text component is used to change its value, nothing else needs to be done. The Abstract Windowing Toolkit handles the updating that is necessary to show the new value.

Quiz

After the LottoMadness program has soured you on games of chance, play a game of skill by answering the following questions.

Questions

1. Why are action events called by that name?

(a) They occur in reaction to something else.
(b) They indicate that some kind of action should be taken in response.
(c) They honor cinematic adventurer Action Jackson.

2. What does this signify as the argument to an addActionListener() method?

(a) "This" listener should be used when an event occurs.
(b) "This" event takes precedence over others.
(c) "This" class of objects will handle the events.

3. Which component stores user input as integers?

(a) TextArea
(b) TextField
(c) Neither does

Answers

1. b.

2.
c. If the name of another class were used as an argument instead of the this statement, that class would receive the events and be expected to handle them.

3.
c. TextField and TextArea components store their values as text, so their values must be converted before they can be used as integers, floating-point numbers, or other nontext values.

Activities

If the main event of this hour didn't provide enough action for your taste, interface with the following activities: