Our previous post describes a JavaFX countdown timer. Let’s take that same program and add a progress bar control. As the timer counts down, the progress bar gradually fills in—as shown in the screenshot here. When the timer begins (at 15), the progress bar is empty (all gray) and when the timer reaches 0, the progress bar is filled in (all blue).
The progress bar includes
progressProperty, a DoubleProperty with values that range between 0 (no progress or 0% complete) and 1 (progress complete or 100%). Half-way is 0.5.
Recall that in our previous post, we bind the numeric label
timerLabel to the timer property (
timeSeconds). Now to configure the progress bar, we bind its
progressProperty to the timer as well. With binding, we don’t have to update
progressProperty in the timeline’s event handler.
Here’s our first attempt.
private static final Integer STARTTIME = 15; private IntegerProperty timeSeconds = new SimpleIntegerProperty(STARTTIME); . . . // Instantiate progressbar and configure binding ProgressBar progressBar = new ProgressBar(); progressBar.progressProperty().bind( timeSeconds.divide(STARTTIME*1.0)); . . . // Add to vertical box layout component vb.getChildren().addAll(button, timerLabel, progressBar); . . .
This is a nice try, but not the result we want at all! We instantiate the progress bar in line 6 and specify its binding in lines 7-8. Recall that
timeSeconds is an IntegerProperty, so we must use Property method
divide() to make sure the binding is a Double whose range is between 0 and 1. Can you guess the outcome?
There are two problems here. First, because our timer updates once a second, the progress bar chunks through (in 15 evenly spaced chunks) instead of filling smoothly. Second, the progress bar begins filled and empties as the timer counts down (we want the opposite behavior). To fix these problems, we must do the following:
- Make our timeline cycle faster. Although the label should only change once per second, the progress bar should be updated more often. We do this by making
timeSeconds1500 instead of 15. Then we divide by 100 when binding to
- Correlate the starting value of the timer (15) to zero for the progress bar and the ending value of the timer (0) to 1.0 for the progress bar. This fills the progress bar as the timer counts down. (In other words, a little bit of math will help us!)
Here’s the updated code.
private static final Integer STARTTIME = 15; private IntegerProperty timeSeconds = new SimpleIntegerProperty(STARTTIME*100); . . . // Bind the timerLabel textProperty // to the timeSeconds property timerLabel.textProperty().bind( timeSeconds.divide(100).asString()); . . . // Bind the progressBar progressProperty // to the timeSeconds property ProgressBar progressBar = new ProgressBar(); progressBar.progressProperty().bind( timeSeconds.divide(STARTTIME*100.0) .subtract(1).multiply(-1)); . . . // Inside the button event handler // Make the timeline duration STARTTIME seconds timeline.getKeyFrames().add( timeSeconds.set((STARTTIME+1)*100); timeline = new Timeline(); timeline.getKeyFrames().add( new KeyFrame(Duration.seconds(STARTTIME+1), new KeyValue(timeSeconds, 0))); timeline.playFromStart(); . . .
This provides the behavior we want. Let’s review these changes.
timeSecondsnow starts off at 1500 instead of 15 (100 times the original initial value) (lines 2-3).
- To compensate for this change to
timeSeconds, we must divide
timeSecondsby 100 to correctly configure the
timerLabelbinding (lines 7-8).
- We divide
timeSecondsby 1500 to calibrate the progress bar. The values now progress from 0% complete to 100% since we subtract one and multiply by -1 (lines 13-15).
Download the FXTimer with ProgressBar JavaFX source code here.