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
timeSeconds
1500 instead of 15. Then we divide by 100 when binding totimerLabel
. - 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.
- IntegerProperty
timeSeconds
now 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 dividetimeSeconds
by 100 to correctly configure thetimerLabel
binding (lines 7-8). - We divide
timeSeconds
by 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.
Pingback: JavaFX links of the week, October 24 // JavaFX News, Demos and Insight // FX Experience
A couple of thoughts:
First, thank you very much for this blog post/tutorial. Your writing is concise and your focus on one “feature” makes it much easier to comprehend and build skills with JavaFX.
Second, I have been using GWT for about a year and despite its ability to make stunning web apps, I have found myself fighting against the framework to get relatively simple things done (especially the new RequestFactory and Editor frameworks). JavaFX looks like a nice alternative (except that it runs as a plugin instead of a website which is both a hindrance for user adoption and a great help for producing an app in a much shorter time span). Do you have or know of any references on how to structure JavaFX apps as web-apps (i.e. that pull and push data to a server)?
Thirdly, I noticed you have not utilized FXML in these posts. While I find it adds a little complexity it seems to separate concerns quite nicely. I have redone this Timer post in FXML if you are interested.
Hi Mike,
Thanks for taking the time to leave a comment! I appreciate the feedback.
After I quick search I found the following set of tutorials which explores building a JavaFX enterprise application here: http://www.zenjava.com/series/building-jee-applications-in-javafx-2-0/. The material looks very helpful.
Yes, I agree using FXML helps separate the view and lets you build an MVC-structured app. Also, the FXML lets you see the hierarchical arrangement of your scenegraph–which helps with maintenance. Here is a link for readers who would like to explore FXML further: http://docs.oracle.com/javafx/2.0/fxml_get_started/why_use_fxml.htm.
As you noted, I was focusing on the JavaFX API in this post. By all means, I encourage you to post the FXML for this Timer example. I think readers would like to see the implementation differences.
Again, thanks for the feedback.
Here is the FXML version (I also just tried converting your sketchpad app with a bit more bells and whistles in the FXML and it works fairly well). One thing to note is that there doesn’t seem to be a way to get a hold of the scene variable to base your components’ sizes off of it. So instead I just hardcoded it either into the VBox or made a javascript with variables set to those values and then referenced those in the components (let me know if there is a better way to do this).
Timer.java:
Timer.fxml:
TimerController.java