> ### Solution for
> Programming Exercise 7.1

> THIS PAGE DISCUSSES ONE POSSIBLE SOLUTION to the following exercise from this on-line Java textbook.

> Exercise 7.1: Exercise 5.2 involved a class, StatCalc.java, that could compute some statistics of a set of numbers. Write an applet that uses the `StatCalc` class to compute and display statistics of numbers entered by the user. The applet will have an instance variable of type `StatCalc` that does the computations. The applet should include a `TextField` where the user enters a number. It should have four labels that display four statistics for the numbers that have been entered: the number of numbers, the sum, the mean, and the standard deviation. Every time the user enters a new number, the statistics displayed on the labels should change. The user enters a number by typing it into the `TextField` and pressing return. There should be a "Clear" button that clears out all the data. This means creating a new `StatCalc` object and resetting the displays on the labels. My applet also has an "Enter" button that does the same thing as pressing the return key in the `TextField`. (Recall that a `TextField` generates an `ActionEvent` when the user presses return, so your applet should register itself to listen for `ActionEvents` from the `TextField`.) Here is my solution to this problem:

> Discussion

> In my applet, I used four labels to display results and another label at the top of the applet to diplay a message to the user. Aside from these labels, one row of the applet holds three other components: a `TextField` and two `Buttons`. The applet as a whole uses a `GridLayout` with six rows. Five of the rows hold `Labels`. The other row contains a `Panel` that holds the `TextField` and `Buttons`. This `Panel` uses a `GridLayout` with three columns and just one row.

> The applet has an `init()` method that creates and lays out the components. Since I want the applet to look nice, I set a background color and a foreground color for most of the components. (The `TextField` and `Buttons` use the default black foreground color.) After looking at my first attempt, I decided to use a Monospaced font for the display labels. In a Monospaced font, all the characters are the same size. This makes it possible to line up the output values vertically by putting the same number of characters in each label. To make it easy to play with the colors and fonts, I declared three named constants

>            final static Color labelBG = new Color(240,225,200);  // beige
>            final static Color labelFG = new Color(180,0,0);  // dark red
>            final static Font labelFont = new Font("Monospaced", Font.PLAIN, 12);

> I could then make one of the labels, such as `countLabel`, with the commands:

>               countLabel = new Label("Number of Entries:   0");
>               countLabel.setBackground(labelBG);
>               countLabel.setForeground(labelFG);
>               countLabel.setFont(labelFont);

> However, since there are four labels to create, I wrote a subroutine to create a display label to show a given string:

>            private Label makeLabel(String text) {
>                   // A utility routine for creating the labels that are used
>                   // for display.  This routine is called by init().
>               Label label = new Label(text);
>               label.setBackground(labelBG);
>               label.setForeground(labelFG);
>               label.setFont(labelFont);
>               return label;
>            }

> Then, in the `init()` method, the labels can be created with four lines, instead of 16:

>               countLabel =   makeLabel(" Number of Entries:  0");
>               sumLabel =     makeLabel(" Sum:                0.0");
>               meanLabel =    makeLabel(" Average:            undefined");
>               standevLabel = makeLabel(" Standard Deviation: undefined");

> Utility routines like `makeLabel()` are very commonly used when there are a lot of similar components to create. Not that when the labels are first created, the text on the labels is appropriate for a dataset that contains zero elements. In particular, if there are no data, the average and standard deviation are undefined.

> The applet registers itself to listen for action events from the `TextField` and from the `Buttons`. In the `actionPerformed()` method, the function `evt.getSource()` is called to find the `Object` that generated the event. This will be either the `numberInput` box, the `enterButton`, or the `clearButton`. The source of the event is checked to decide how to respond. (This is an alternative to checking the action command.)

> If the user clicked the "Clear" button, the response is to create a new `StatCalc` object and to reset the display labels to reflect the fact that there is no data in the dataset. It's important to understand the effect of the command "`stats = new StatCalc();`". The applet will continue to use the same `StatCalc` variable, `stats`. However, now the variable refers to a new `StatCalc` object. The new object does not yet have any data in its dataset. The next time the user enters a number, the dataset will get its first value. Always keep in mind the difference between variables and objects. Also, keep in mind that you have to think in terms of changing the state of the applet in response to events. I change the applet's state by starting to use a new `StatCalc` object, and the display labels are changed to keep them consistent with the new state.

> When the user clicks the "Enter" button or presses return in the `TextField`, we have to get the user's input and add it to the `StatCalc` object. This will cause the values of the four statistics to change. We have to change the display labels to show the new values. The code for getting the user's number from the input box comes from Section 7.4. It includes a check to make sure that the user's input is a legal number. If the input is not legal, then I show an error message in the `Label` named `message` and return from the `actionPerformed()` method without entering any new data:

>           double num;  // The user's number.
>           try {
>              Double d = new Double( numberInput.getText() );
>              num = d.doubleValue();
>           }
>           catch (NumberFormatException e) {
>                  // The user's entry is not a legal number.
>                  // Put an error message in the message label
>                  // and return without entering a number.
>              message.setText("\"" + numberInput.getText() +
>                                  "\" is not a legal number.");
>              numberInput.selectAll();
>              numberInput.requestFocus();
>              return;
>           }

> The commands "`numberInput.selectAll();`" and "`numberInput.requestFocus();`" are there as a convenience for the user. The first command selects all the text in the number input box. The second command gives the input focus to the input box. That way, the user can just start typing the next number, without having to click on the input box or erase the content of the box. (Since the contents of the box are selected, they will disappear automatically when the user starts typing, to be replaced with the new input. A surprising number of people have never learned that text selections work this way.)

> Once we have the user's number, the command "`stats.enter(num);`" adds the number `num` into the dataset. The statistics about the data set can be obtained by calling the functions `stats.getCount()`, `stats.getSum()`, `stats.getMean()`, and `stats.getStandardDeviation()`. This information can be found by reading the source code for the `StatCalc` class.

> The Solution

>         /*
>            In this applet, the user enters numbers in a text field box.
>            After entering each number, the user presses return (or clicks
>            on a button).  Some statistics are displayed about all the
>            numbers that the user has entered.
>         */

>         import java.awt.*;
>         import java.awt.event.*;
>         import java.applet.*;

>         public class StatsApplet extends Applet implements ActionListener {

>            final static Color labelBG = new Color(240,225,200);
>            final static Color labelFG = new Color(180,0,0);
>            final static Font labelFont = new Font("Monospaced", Font.PLAIN, 12);

>            Label countLabel;    // A label for displaying the number of numbers.
>            Label sumLabel;      // A label for displaying the sum of the numbers.
>            Label meanLabel;     // A label for displaying the average.
>            Label standevLabel;  // A label for displaying the standard deviation.

>            Label message;  // A message at the top of the applet.  It will
>                            //   show an error message if the user's input is
>                            //   not a legal number.  Otherwise, it just tells
>                            //   the user to enter a number and press return.

>            Button enterButton;   // A button the user can press to enter a number.
>                                  //    This is an alternative to pressing return.
>            Button clearButton;   // A button that clears all the data that the
>                                  //    user has entered.

>            TextField numberInput;  // The input box where the user enters numbers.

>            StatCalc stats;  // An object that keeps track of the statistics
>                             //   for all the numbers that have been entered.

>            public void init() {

>               /* Create all the objects used by the applet.  The applet
>                  will listen for action events from the button and from
>                  the TextField.  A TextField generates an ActionEvent
>                  when the user presses return.  */

>               stats = new StatCalc();

>               numberInput = new TextField();
>               numberInput.setBackground(Color.white);
>               numberInput.addActionListener(this);

>               enterButton = new Button("Enter");
>               enterButton.setBackground(Color.lightGray);
>               enterButton.addActionListener(this);

>               clearButton = new Button("Clear");
>               clearButton.setBackground(Color.lightGray);
>               clearButton.addActionListener(this);

>               Panel inputPanel = new Panel();  // A panel that will hold the
>                                                //   TextField and Buttons.
>               inputPanel.setLayout( new GridLayout(1,3,2,2) );
>               inputPanel.add(numberInput);
>               inputPanel.add(enterButton);
>               inputPanel.add(clearButton);

>               countLabel =   makeLabel(" Number of Entries:  0");
>               sumLabel =     makeLabel(" Sum:                0.0");
>               meanLabel =    makeLabel(" Average:            undefined");
>               standevLabel = makeLabel(" Standard Deviation: undefined");

>               message = new Label("Enter a number, press return:",
>                                              Label.CENTER);
>               message.setBackground(labelBG);
>               message.setForeground(Color.blue);
>               message.setFont(new Font("SansSerif", Font.BOLD, 12));

>               /* Use a GridLayout with 6 rows and 1 column, and add all the
>                  components that have been created to the applet. */

>               setBackground(Color.blue);
>               setLayout( new GridLayout(6,1,2,2) );
>               add(message);
>               add(inputPanel);
>               add(countLabel);
>               add(sumLabel);
>               add(meanLabel);
>               add(standevLabel);

>            } // end init();

>            private Label makeLabel(String text) {
>                   // A utility routine for creating the labels that are used
>                   // for display.  This routine is called by init().
>               Label label = new Label(text);
>               label.setBackground(labelBG);
>               label.setForeground(labelFG);
>               label.setFont(labelFont);
>               return label;
>            }

>            public Insets getInsets() {
>                  // Leave a 2-pixel border around the edges of the applet.
>               return new Insets(2,2,2,2);
>            }

>            public void actionPerformed(ActionEvent evt) {
>                  // This is called when the user clicks one of the buttons or
>                  // presses return in the TextArea.  The response to clicking
>                  // on the Enter button is the same as the response to pressing
>                  // return in the TextField.

>               Object source = evt.getSource();  // Object that generated
>                                                 //   the action event.

>               if (source == clearButton) {
>                     // Handle the clear button by starting with a new,
>                     // empty StatCalc object and resetting the display
>                     // labels to show no data entered.  The TextField
>                     // is also made empty.
>                  stats = new StatCalc();
>                  countLabel.setText(" Number of Entries:  0");
>                  sumLabel.setText(" Sum:                0.0");
>                  meanLabel.setText(" Average:            undefined");
>                  standevLabel.setText(" Standard Deviation: undefined");
>                  numberInput.setText("");
>               }
>               else if (source == enterButton || source == numberInput) {
>                     // Get the user's number, enter it into the StatCalc
>                     // object, and set the display on the display labels
>                     // to reflect the new data.
>                  double num;  // The user's number.
>                  try {
>                     Double d = new Double( numberInput.getText() );
>                     num = d.doubleValue();
>                  }
>                  catch (NumberFormatException e) {
>                         // The user's entry is not a legal number.
>                         // Put an error message in the message label
>                         // and return without entering a number.
>                     message.setText("\"" + numberInput.getText() +
>                                          "\" is not a legal number.");
>                     numberInput.selectAll();
>                     numberInput.requestFocus();
>                     return;
>                  }
>                  stats.enter(num);
>                  countLabel.setText(" Number of Entries:  " + stats.getCount());
>                  sumLabel.setText(" Sum:                " + stats.getSum());
>                  meanLabel.setText(" Average:            " + stats.getMean());
>                  standevLabel.setText(" Standard Deviation: "
>                                                           + stats.getStandardDeviation());
>               }

>               /* Set the message label back to its normal text, in case it has
>                  been showing an error message.  For the user's convenience,
>                  select the text in the TextField and give the input focus
>                  to the text field.  That way the user can just start typing
>                  the next number. */

>               message.setText("Enter a number, press return:");
>               numberInput.selectAll();
>               numberInput.requestFocus();

>            }  // end ActionPerformed

>         }  // end StatsApplet

> [ Exercises | Chapter Index | Main Index ]