[ Exercises | Chapter Index | Main Index ]

Solution for Programming Exercise 13.2


This page contains a sample solution to one of the exercises from Introduction to Programming Using Java.


Exercise 13.2:

The StopWatchLabel component from Subsection 13.3.1 displays the text "Timing..." when the stopwatch is running. It would be nice if it displayed the elapsed time since the stopwatch was started. For that, you need to create an AnimationTimer. (See Subsection 6.3.5.) Add an AnimationTimer to the original source code, StopWatchLabel.java, to drive the display of the elapsed time in seconds.


Discussion

This one is really too easy! As suggested, I added an AnimationTimer, named timer, as an instance variable. (I also changed some of the occurrences of the word "timer" in the original program to "stopwatch" to try to avoid some confusion: the stopwatch is a kind of timer, and we are using another kind of timer to implement it.) When the stopwatch starts running, it is necessary to call timer.start() to start the animation timer running, and when the stopwatch stops running, timer.stop() must be called. The only other thing that is needed is to create the AnimationTimer object. Since AnimationTimer is an abstract class, we need a subclass, and we need to provide a definition for the abstract method handle(). That method, which will be called many times per second, can update the label to show the elapsed time. Using an anonymous class,

timer = new AnimationTimer() {
    public void handle(long now) {
        long elapsedTime = System.currentTimeMillis() - startTime;
        String text = String.format(
                 "%3.1f seconds elapsed", elapsedTime/1000.0);
        setText(text);
    }
};

In the program, I don't create the animation timer until the first time the stopwatch is started. Note that when the stopwatch is stopped, the program displays the time with three digits after the decimal point. While it's running, it doesn't make sense to be that accurate since the display won't be updated every millisecond. So, I only use one digit after the decimal point when the stopwatch is running.


The Solution

The timer component, with changes from the original shown in red:


import javafx.scene.control.Label;
import javafx.animation.AnimationTimer;

/**
 * A custom component that acts as a simple stopwatch.  When the user clicks
 * on it, this component starts timing.  When the user clicks again,
 * it displays the time between the two clicks.  Clicking a third time
 * starts another timer, etc.  While it is timing, the label
 * displays the number of seconds that have passed since the stop
 * watch was started.
 */
public class StopWatchLabel2 extends Label {

    private long startTime;   // Start time of timer.
                              //   (Time is measured in milliseconds.)

    private boolean running;  // True when the timer is running.
    
    private AnimationTimer timer;  // Used to update the timer to show
                                   // the number of seconds that have passed.

    /**
     * Constructor sets initial text on the label to
     * "Click to start timer." and sets up a mouse event
     * handler so the label can respond to clicks.
     */
    public StopWatchLabel2() {
        super("  Click to start timer.  ");
        setOnMousePressed( e -> setRunning( !running ) );
    }


    /**
     * Tells whether the timer is currently running.
     */
    public boolean isRunning() {
        return running;
    }


    /**
     * Sets the stop watch to be running or stopped, and changes the text that
     * is shown on the label.  (This method should be called on the JavaFX
     * application thread.)
     * @param running says whether the stopwatch should be running; if this
     *    is equal to the current state, nothing is done.
     */
    public void setRunning( boolean running ) {
        if (this.running == running)
            return;
        this.running = running;
        if (running == true) {
                // Record the time and start the stopwatch.
            startTime = System.currentTimeMillis();
            if (timer == null) {
                timer = new AnimationTimer() {
                    public void handle(long now) {
                        long elapsedTime = System.currentTimeMillis() - startTime;
                        String text = String.format(
                                 "%3.1f seconds elapsed", elapsedTime/1000.0);
                        setText(text);
                    }
                };
            }
            setText("   0.0 seconds elapsed");
            timer.start();
        }
        else {
                // Stop the stopwatch.  Compute the elapsed time since the
                // stopwatch was started and display it.
            running = false;
            timer.stop();
            long endTime = System.currentTimeMillis();
            double seconds = (endTime - startTime) / 1000.0;
            setText( String.format("Time: %1.3f seconds", seconds) );
        }
    }

} // end StopWatchLabel

The code for the program that tests the component:


import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.layout.StackPane;
import javafx.geometry.Pos;

/**
 * Shows a StopWatchLabel2.  The user can click to start a timer
 * and click again to stop it.  The elapsed time is displayed.
 */
public class TestStopWatch2 extends Application {

    public static void main(String[] args) {
        launch(args);
    }
    //------------------------------------------------------------
    
    public void start(Stage stage) {
        
        StopWatchLabel2 stopWatch = new StopWatchLabel2();
        stopWatch.setStyle("-fx-font: bold 30pt serif; -fx-background-color:#ffffee;"
                + "-fx-border-color:#008; -fx-border-width:3px; -fx-padding:20px;"
                + "-fx-text-fill: #008");
        stopWatch.setMaxSize(Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY);
        stopWatch.setAlignment(Pos.CENTER);
        
        stage.setScene( new Scene(new StackPane(stopWatch)) );
        stage.setTitle("StopWatchLabel Demo");
        stage.show();
        
    }
    
}

[ Exercises | Chapter Index | Main Index ]