Solution for Programmming Exercise 6.7

Exercise 6.7:

Exercise 5.2 involved a class, StatCalc.java,
that could compute some statistics
of a set of numbers. Write a program that uses the StatCalc class to
compute and display statistics of numbers entered by the user. The panel will
have an instance variable of type StatCalc that does the computations.
The panel should include a JTextField 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
JTextField 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 panel also has an "Enter" button that
does the same thing as pressing the return key in the JTextField.
(Recall that a JTextField generates an ActionEvent when the
user presses return, so your panel should register itself to listen for
ActionEvents from the JTextField.)
Write your program as a stand-alone application. Here is an applet version of my solution to
this problem:

Discussion

In my solution, I used four labels to display results and another label at the
top of the applet to display a message to the user. Aside from these labels,
one row of the applet holds three other components: a JTextField and
two JButtons. The content pane of the applet, which holds all the
components, uses a GridLayout with six rows. Five of the rows hold
JLabels. The other row contains a JPanel that holds the
JTextField and JButtons. This JPanel uses a
GridLayout with three columns and just one row.

The constructor that creates and lays out the
components. Since I want the program to look nice, I set a background color and
a foreground color for most of the components. I set the labels to
be opaque, to make sure that the background of each label will actually be
filled in with the label's background 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

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

/**
* A utility routine for creating the labels that are used
* for display. This routine is called by the constructor.
* @param text The text to show on the label.
*/
private JLabel makeLabel(String text) {
JLabel label = new JLabel(text);
label.setBackground(labelBG);
label.setForeground(labelFG);
labe.setOpaque(true);
label.setFont(labelFont);
return label;
}

Then in the constructor, the labels can be created with four
lines, instead of 16:

Utility routines like makeLabel() are very commonly used when there
are a lot of similar components to create. Note 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 panel registers itself to listen for action events from the
JTextField and from the JButtons. 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 event's 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 panel will continue to
use the same StatCalcvariable, stats. However, now
the variable refers to a new StatCalcobject. 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 panel in response to events. I
change the panel'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
JTextField, 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 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 JLabel named message and return from the
actionPerformed() method without entering any new data:

The commands "numberInput.selectAll();" and
"numberInput.requestFocus();" are there as a convenience for the user.
The first command, which was not covered in this chapter,
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.

In addition to the StatCalc class and the panel class shown below, you will need a main routine before
you can run the program. As usual, this can go in a separate class, such as:

import javax.swing.JFrame;
/**
* A stand-alone application that let's the user input some numbers and
* compute some statistics The main program just opens a window
* that shows a StatsPanel, which does all the work.
*/
public class Stats {
public static void main(String[] args) {
JFrame window = new JFrame("Simple Stats Calculator");
StatsPanel content = new StatsPanel();
window.setContentPane(content);
window.pack(); // Set to preferred size of contents
window.setLocation(100,100);
window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
window.setVisible(true);
}
}

The Solution

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* In this panel, 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.
*/
public class StatsPanel extends JPanel implements ActionListener {
final static Color labelBG = new Color(240,225,200); // For creating labels
final static Color labelFG = new Color(180,0,0);
final static Font labelFont = new Font("Monospaced", Font.PLAIN, 12);
private JLabel countLabel; // A label for displaying the number of numbers.
private JLabel sumLabel; // A label for displaying the sum of the numbers.
private JLabel meanLabel; // A label for displaying the average.
private JLabel standevLabel; // A label for displaying the standard deviation.
private JLabel 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.
private JButton enterButton; // A button the user can press to enter a number.
// This is an alternative to pressing return.
private JButton clearButton; // A button that clears all the data that the
// user has entered.
private JTextField numberInput; // The input box where the user enters numbers.
private StatCalc stats; // An object that keeps track of the statistics
// for all the numbers that have been entered.
/**
* The constructor creates the objects used by the panel. The panel
* will listen for action events from the buttons and from the text
* field. (A JTextField generates an ActionEvent when the user presses
* return while typing in the text field.)
*/
public StatsPanel() {
stats = new StatCalc();
numberInput = new JTextField();
numberInput.setBackground(Color.WHITE);
numberInput.addActionListener(this);
enterButton = new JButton("Enter");
enterButton.addActionListener(this);
clearButton = new JButton("Clear");
clearButton.addActionListener(this);
JPanel inputPanel = new JPanel(); // A panel that will hold the
// JTextField and JButtons.
inputPanel.setLayout( new GridLayout(1,3) );
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 JLabel("Enter a number, press return:",
JLabel.CENTER);
message.setBackground(labelBG);
message.setForeground(Color.BLUE);
message.setOpaque(true);
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);
/* Add a blue border around the panel. */
setBorder( BorderFactory.createLineBorder(Color.BLUE, 2) );
} // end constructor
/**
* A utility routine for creating the labels that are used
* for display. This routine is called by the constructor.
* @param text The text to show on the label.
*/
private JLabel makeLabel(String text) {
JLabel label = new JLabel(text);
label.setBackground(labelBG);
label.setForeground(labelFG);
label.setFont(labelFont);
label.setOpaque(true);
return label;
}
/**
* This is called when the user clicks one of the buttons or
* presses return in the input box. The response to clicking
* on the Enter button is the same as the response to pressing
* return in the JTextField.
*/
public void actionPerformed(ActionEvent evt) {
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 {
num = Double.parseDouble(numberInput.getText());
}
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