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 ]