Fast counting timer in JavaFX - java

For effect, I want a label to display time like a stop watch would. The time starts at 0, and ends somewhere around 2 or 3 seconds. The service is stopped when desired. The issue I am having, is because I am trying to update the text 1000 times per second, the timer is lagging behind.
Like I said, this is for effect and will be hidden as soon as the timer stops, but the time should be fairly accurate, not 3 seconds behind.
Is there any way I can make this faster? I would like to have all 4 decimal places if possible.
timer = new Label();
DoubleProperty time = new SimpleDoubleProperty(0.0);
timer.textProperty().bind(Bindings.createStringBinding(() -> {
return MessageFormat.format(gui.getResourceBundle().getString("ui.gui.derbydisplay.timer"), time.get());
}, time));
Service<Void> countUpService = new Service<Void>() {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
while (!isCancelled()) {
Platform.runLater(() -> time.set(time.get() + 0.0001));
Thread.sleep(1);
}
return null;
}
};
}
};

The main reason your clock lags is because you increase the time by 0.0001 seconds (i.e. 1/10000 seconds) 1000 times per second. So every second it will increase by 0.1... But even if you fix that issue, it would still lag a small amount because you don't account for the time it takes to make the method calls. You can fix this by checking the system clock when you start, and then checking it every time you do the update.
Updating the label 1000 times per second is pretty much redundant, because JavaFX only aims to update the UI 60 times per second. (It will be slower if the UI thread is busy trying to do too much stuff, which you also make happen by scheduling so many calls to Platform.runLater().) You can fix this by using an AnimationTimer. The AnimationTimer.handle(...) method is called once every time a frame is rendered, so this effectively updates as often as JavaFX allows the UI to update.
AnimationTimer timer = new AnimationTimer() {
private long startTime ;
#Override
public void start() {
startTime = System.currentTimeMillis();
super.start();
}
#Override
public void handle(long timestamp) {
long now = System.currentTimeMillis();
time.set((now - startTime) / 1000.0);
}
};
You can start this with timer.start(); and stop it with timer.stop();. Obviously you can add more functionality to set a time for it to run, etc, and call stop() from the handle(...) method if you need.
SSCEE:
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Timer extends Application {
#Override
public void start(Stage primaryStage) {
Label label = new Label();
DoubleProperty time = new SimpleDoubleProperty();
label.textProperty().bind(time.asString("%.3f seconds"));
BooleanProperty running = new SimpleBooleanProperty();
AnimationTimer timer = new AnimationTimer() {
private long startTime ;
#Override
public void start() {
startTime = System.currentTimeMillis();
running.set(true);
super.start();
}
#Override
public void stop() {
running.set(false);
super.stop();
}
#Override
public void handle(long timestamp) {
long now = System.currentTimeMillis();
time.set((now - startTime) / 1000.0);
}
};
Button startStop = new Button();
startStop.textProperty().bind(
Bindings.when(running)
.then("Stop")
.otherwise("Start"));
startStop.setOnAction(e -> {
if (running.get()) {
timer.stop();
} else {
timer.start();
}
});
VBox root = new VBox(10, label, startStop);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 320, 120);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The value passed into the handle(...) method is actually a timestamp in nanoseconds. You could use this and set the startTime to System.nanoTime(), though the precision you get there is way more than you can realistically use when the frames render at maximum of 60 frames per second.

Related

How to restart a timer in JavaFX?

I'm currently working on a program in which users can create their on time intervals for different exercises. Once start is pressed, the countdown begins for the first exercise. Once it is done, a sound is played and countdown begins for the second one and so on until all the exercises are done and removed. I use a timer which after every 1 second, subtracts the time of the exercise by 1. The problem is, I can't seem to find a way to restart Timers in java. When all exercises are done I can stop the timer but I can't seem to find a way to restart it for when I want to create new exercises and go through the process again. I can't also find a way to pause and play the timer again during a particular process. I'm new to JavaFX, so I would really appreciate if you could guide me how I can change my code to achieve what I'm looking for.
Timer timer = new Timer();
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
//Timer task=new TimerTask();
timer.schedule(new TimerTask() {
#Override
public void run() {
running=true;
if (running==true)
{
if (workoutsList.size() == 0) {
return;
}
if (workoutsList.size() == 1 && workoutsList.get(0).time == 1) {
text.setText("over!");
mediaPlayer1.play();
workoutsList.clear();
workouts.getItems().clear();
timer.cancel();
return;
}
workoutsList.get(0).time -= 1;
if (workoutsList.get(0).time == 0) {
workoutsList.remove(0);
mediaPlayer.play();
return;
}
workouts.getItems().clear();
workouts.refresh();
for (int i = 0; i < workoutsList.size(); i++) {
workouts.getItems().add(workoutsList.get(i));
}
}
}
}, 0, 1000);
}
});
stopButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
timer.cancel();
running=false;
}
});
Since the Timer does nothing except track time it would be better to use the javafx.animation API. This gives you certain advantages:
Everything happens on the JavaFX Application Thread, meaning no concurrency issues.
You can make use of the currentTime and cycleDuration properties of Animation to track the time left in the countdown.
You can make use of the play(), pause(), and stop() methods of Animation to control the timer.
You can use the onFinished property of Animation to play you sound when the timer completes.
Here's an example using PauseTransition, though you could also use e.g. Timeline.
import javafx.animation.Animation;
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
// javafx.util.Duration
PauseTransition timer = new PauseTransition(Duration.minutes(5));
// timer.setOnFinished(e -> /* play sound */);
Button startBtn = new Button("Start");
startBtn.setOnAction(e -> timer.play());
Button pauseBtn = new Button("Pause");
pauseBtn.setOnAction(e -> timer.pause());
Button resetBtn = new Button("Reset");
resetBtn.setOnAction(e -> timer.stop());
Label label = new Label();
label.setFont(Font.font("Monospaced", 20));
label.textProperty().bind(timeLeftAsString(timer));
HBox hbox = new HBox(10, startBtn, pauseBtn, resetBtn);
hbox.setAlignment(Pos.CENTER);
VBox root = new VBox(25, label, hbox);
root.setPadding(new Insets(25));
root.setAlignment(Pos.CENTER);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private StringBinding timeLeftAsString(Animation animation) {
return Bindings.createStringBinding(
() -> {
double currentTime = animation.getCurrentTime().toMillis();
double totalTime = animation.getCycleDuration().toMillis();
long remainingTime = Math.round(totalTime - currentTime);
// java.time.Duration
java.time.Duration dur = java.time.Duration.ofMillis(remainingTime);
return String.format(
"%02d:%02d:%03d", dur.toMinutes(), dur.toSecondsPart(), dur.toMillisPart());
},
animation.currentTimeProperty(),
animation.cycleDurationProperty());
}
}
Side note: You mention a sound is played when the timer completes, and I can see a call to mediaPlayer.play(). Considering the nature of the program I assume the sound being played is relatively short. If that's the case you should consider using AudioClip instead of MediaPlayer.

How to update progress bar [duplicate]

I'm trying to update a progress bar in Java FX. My first problem was that the window said "not responding" instead of actually updating. It just froze and then after the tasks were done, the progress bar became full. So I found out that I had to use multithreading and implemented it like this.
overallList.clear();
progressbar.setprogress(0);
for(Object obj : list) {
class ThreadProgress implements Runnable { // inner class
public void run() {
thisList = scrape(obj);
overallList.add(thisList);
progressbar.setProgress(progressbar.getProgress() + (double)1/size);
}
}
Thread current = new Thread(new ThreadProgress());
current.start();
}
textAreaConsole.setText("Total number of things:" + overallList.size());
But now the problem is the final line prints "Total number of things: 0" because the threads don't actually finish executing before the machine runs the final line. Then I found out multiple ways to fix this, specifically using join() or ExecutorService. I implemented join() like this.
overallList.clear();
progressbar.setprogress(0);
List<Thread> threads = new ArrayList<Thread>();
for(Object obj : list) {
class ThreadProgress implements Runnable { // inner class
public void run() {
thisList = scrape(obj);
overallList.add(thisList);
progressbar.setProgress(progressbar.getProgress() + (double)1/size);
}
}
Thread current = new Thread(new ThreadProgress());
current.start();
threads.add(current);
}
for(Thread thread : threads) thread.join(); // with a try-catch loop
textAreaConsole.setText("Total number of things:" + overallList.size());
But this brings me back to the original problem, the window says "not responding" again. Same thing happened with ExecutorService. I have no idea what to do now.
See the example application below. It provides a simple ProgressBar and a Label to demonstrate how to update the UI with the progress of a background Task.
The code is commented as well.
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ProgressBarExample extends Application {
// Create our ProgressBar
private ProgressBar progressBar = new ProgressBar(0.0);
// Create a label to show current progress %
private Label lblProgress = new Label();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Simple interface
VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
// Button to start the background task
Button button = new Button("Start");
button.setOnAction(event -> startProcess());
// Add our controls to the scene
root.getChildren().addAll(
progressBar,
new HBox(5) {{
setAlignment(Pos.CENTER);
getChildren().addAll(
new Label("Current Step:"),
lblProgress
);
}},
button
);
// Here we will
// Show the Stage
primaryStage.setWidth(300);
primaryStage.setHeight(300);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private void startProcess() {
// Create a background Task
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
// Set the total number of steps in our process
int steps = 1000;
// Simulate a long running task
for (int i = 0; i < steps; i++) {
Thread.sleep(10); // Pause briefly
// Update our progress and message properties
updateProgress(i, steps);
updateMessage(String.valueOf(i));
}
return null;
}
};
// This method allows us to handle any Exceptions thrown by the task
task.setOnFailed(wse -> {
wse.getSource().getException().printStackTrace();
});
// If the task completed successfully, perform other updates here
task.setOnSucceeded(wse -> {
System.out.println("Done!");
});
// Before starting our task, we need to bind our UI values to the properties on the task
progressBar.progressProperty().bind(task.progressProperty());
lblProgress.textProperty().bind(task.messageProperty());
// Now, start the task on a background thread
new Thread(task).start();
}
}
Edit: Added the setOnFailed() and setOnSucceeded() methods.

creating simple stopwatch - javaFX

I try to avoid asking for help and do as much as I can by myself. But I half been working on this for far too long. I don't even understand what is wrong. oh and when I try to test it my GUI freezes
I have to make a basic stop watch: start, stop, resume, and reset. and yet i can't do it. Please if you would also comment or explain your code, because I feel clueless. (some of my comments are past code I thought would work but didn't)
package gameclock;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Timer;
import java.util.TimerTask;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class GameClock extends Application {
public void start(Stage primaryStage) {
HBox pane = new HBox();
Scene scene = new Scene(pane, 400, 100);
Button start = new Button("Start");
Button stop = new Button("Stop");
Button resume = new Button("Resume");
Button reset = new Button("Reset");
final TextField display = new TextField("0");
display.setEditable(false);
boolean onOff = false;
DateFormat df = new SimpleDateFormat("ss");
Calendar calobj = Calendar.getInstance();
System.out.println(df.format(calobj.getTime()));
start.setOnAction((ActionEvent event) ->
{
int time = Integer.parseInt(display.getText());
/*
Timer t = new Timer();
final int time1 = 1;
t.scheduleAtFixedRate(new TimerTask()
{
public void run()
{
time = time1 + 1;
}
},0,0);
display.setText(String.valueOf(time1));
*/
long timestamp = System.currentTimeMillis(); //divide by 1000 cause a millisecond is 1000 of a second
while (!onOff)
{
if(System.currentTimeMillis() - timestamp > 1000)
{
time++;
display.setText(String.valueOf(time));
}
}
String time2String = Long.toString(timestamp);
});
stop.setOnAction((ActionEvent event) ->
{
if(onOff == false)
onOff(true);
});
resume.setOnAction((ActionEvent event) ->
{
onOff(true);
});
pane.getChildren().addAll(start, display, resume, stop, reset);
pane.setAlignment(Pos.CENTER);
pane.setPadding(new Insets(0, 0, 0, 0));
primaryStage.setTitle("Stop Watch");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
private void onOff(boolean b) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}
You're blocking the application thread by adding the loop directly to the event handler. This results in the UI not being updated and no further events being handled.
Better use a AnimationTimer:
AnimationTimer timer = new AnimationTimer() {
private long timestamp;
private long time = 0;
private long fraction = 0;
#Override
public void start() {
// current time adjusted by remaining time from last run
timestamp = System.currentTimeMillis() - fraction;
super.start();
}
#Override
public void stop() {
super.stop();
// save leftover time not handled with the last update
fraction = System.currentTimeMillis() - timestamp;
}
#Override
public void handle(long now) {
long newTime = System.currentTimeMillis();
if (timestamp + 1000 <= newTime) {
long deltaT = (newTime - timestamp) / 1000;
time += deltaT;
timestamp += 1000 * deltaT;
display.setText(Long.toString(time));
}
}
};
This will run the handle method on the application thread every frame.
You can use start() and stop() to start and stop the stopwatch.
I can recognize an infinite loop with the best of them:
while (!onOff)
{
if(System.currentTimeMillis() - timestamp > 1000)
{
time++;
display.setText(String.valueOf(time));
}
}
I don't know much about JavaFX as I do Java/Android, but there's nothing that's going to toggle onOff from false to true within that loop. Even if you have another button callback to toggle onOff to true, it won't get called because your UI thread is stuck in the loop above for the start button action.
Update:
Now that I see what you are trying to do: Update a text value with the current time, what you want to be using is the notion of a timer. A timer is UI concept in which you get called back in the future while still allowing your UI to be responsive. A quick google of "JavaFX timer" reveals lots of hits on how to do this.

Javafx live thread updates

I'm working with Javafx and threads simultaneously and I constanly run into this problem where I make a button and then when the button is clicked (using event handlers) I make a for loop that changes the button to 1,2,3,4,5 and then delays for a second in the middle of each. Like a count down!
But what happens is it delays for 5 seconds and changes the text of button to 5.
The problem is I want to see it change between 1 and 5 but all I see is 5 at the end of a 5 second delay. I would assume that it changing the button text but I don't see it. I might have to to do with the .show() method in the Javafx class.
public class HewoWorld extends Application implements EventHandler<ActionEvent>
{
Thread t = new Thread();
Button butt;
boolean buttWasClicked = false;
Circle circ1 = new Circle(40, 40, 30, Color.RED);
Circle circ2 = new Circle(100, 100, 30, Color.BLUE);
Group root;
Scene scene;
Stage disStage = new Stage();
int i = 1;
public static void main(String[] args)
{
launch(args);
}
public void start(Stage stage) throws Exception
{
disStage.setTitle("tests stuffs");
Screen screen = Screen.getPrimary();
Rectangle2D bounds = screen.getVisualBounds();
double windh = bounds.getHeight()/2+150;//sets height of screen
double windw = bounds.getWidth()/3;//sets width of screen
Pane layout = new Pane();
butt = new Button();
butt.setText("Hello world");
root = new Group(circ1, circ2, butt);
scene = new Scene(root, 800, 400);
disStage.setWidth(windw);
disStage.setHeight(windh);
butt.setLayoutX(200);
butt.setLayoutY(200);
butt.setOnAction(this);
disStage.setScene(scene);
disStage.show();
}
public void handle(ActionEvent event)
{
if (event.getSource() == butt && buttWasClicked == false)
{
try
{
butt.setText(i+"");
t.sleep(1000);
i++;
}
catch(Exception q)
{
}
circ1 = new Circle(40, 40, 30, Color.BLACK);
circ2 = new Circle(100, 100, 30, Color.RED);
}
}
}
Why your code doesn't work
The reason your code doesn't work is that you are blocking the FX Application Thread.
Like (almost?) all UI toolkits, JavaFX is a single-threaded UI toolkit. This means that all event handlers, and all the rendering of the UI, are performed on a single thread (called the FX Application Thread).
In your code, you have an event handler that takes more than a second to run, because it pauses for a second via a call to Thread.sleep(...). While that event handler is running, the UI cannot be redrawn (because a single thread cannot do two things at once). So while the value of the button's text has changed immediately, the new value won't actually be rendered on the screen until the handle(...) method has finished running. If you had a for loop in the handle method, nothing would be rendered until the entire loop (and anything else in the method) had completed.
How to fix it
The simplest way to do what you want in JavaFX is to use a Timeline to handle the pause. The Timeline manages the threading appropriately for you:
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class CountingButton extends Application {
#Override
public void start(Stage primaryStage) {
Button button = new Button("Count");
Timeline timeline = new Timeline();
for (int count = 0; count <= 5 ; count++) {
final String text = Integer.toString(count);
KeyFrame frame = new KeyFrame(Duration.seconds(count), event ->
button.setText(text));
timeline.getKeyFrames().add(frame);
}
button.setOnAction(e -> timeline.play());
primaryStage.setScene(new Scene(new StackPane(button), 120, 75));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
In general, for changing the appearance of the user interface at specific time points, the JavaFX Animation API (see also the tutorial) can be useful, especially Timeline and PauseTransition.
A "lower-level" way to do this would be to create a Thread yourself and pause in that thread. This is much more advanced: you need to be careful to update the UI on the FX Application Thread, not on the thread you created. You can do this with a call to Platform.runLater(...):
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class CountingButton extends Application {
#Override
public void start(Stage primaryStage) {
Button button = new Button("Start");
button.setOnAction(e -> {
Thread thread = new Thread(() -> {
for (int i = 0; i <= 5 ; i++) {
final String text = "Count: "+i ;
Platform.runLater(() -> button.setText(text));
try {
Thread.sleep(1000);
} catch (InterruptedException exc) {
exc.printStackTrace();
}
}
});
thread.start();
});
primaryStage.setScene(new Scene(new StackPane(button), 120, 75));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
For more general information on threading in JavaFX, have a look at this post: Using threads to make database requests
What you have to do is to replace the thread use by the following method :
scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(
new Runnable(){
#Override
public void run() {
Platform.runLater(new Runnable() {
#Override
public void run() {
//Here your code to change the number by for example incrementig the value of the button
}
});
}
},
1000,
80,
TimeUnit.MILLISECONDS);
+1 if it helps :D
In this case you need a timer to run every second and increment a counter on every hit. To my knowledge, the best way to make a timer in javafx is to use a timeline. https://stackoverflow.com/a/9966213/4683264.
int i = 0;// class field
// ....
Timeline fiveSecondsWonder = new Timeline(new KeyFrame(Duration.seconds(1), event ->
button.setText(++i)));
fiveSecondsWonder.setCycleCount(5);// repeat five times
fiveSecondsWonder.play();

Updating UI from different threads in JavaFX

I am developing an application with several TextField objects that need to be updated to reflect changes in associated back-end properties. The TextFields are not editable, only the back-end may change their content.
As I understand, the correct way about this is to run the heavy computation on a separate thread so as not to block the UI. I did this using javafx.concurrent.Task and communicated a single value back to the JavaFX thread using updateMessage(), which worked well. However, I need more than one value to be updated as the back-end does its crunching.
Since the back-end values are stored as JavaFX properties, I tried simply binding them to the textProperty of each GUI element and let the bindings do the work. This doesn't work, however; after running for a few moments, the TextFields stop updating even though the back-end task is still running. No exceptions are raised.
I also tried using Platform.runLater() to actively update the TextFields rather than binding. The issue here is that the runLater() tasks are scheduled faster than the platform can run them, and so the GUI becomes sluggish and needs to time to "catch up" even after the back-end task is finished.
I found a few questions on here:
Logger entries translated to the UI stops being updated with time
Multithreading in JavaFX hangs the UI
but my issue persists.
In summary: I have a back-end making changes to properties, and I want those changes to appear on the GUI. The back-end is a genetic algorithm, so its operation is broken down into discrete generations. What I would like is for the TextFields to refresh at least once in between generations, even if this delays the next generation. It is more important that the GUI responds well than that the GA runs fast.
I can post a few code examples if I haven't made the issue clear.
UPDATE
I managed to do it following James_D's suggestion. To solve the issue of the back-end having to wait for the console to print, I implemented a buffered console of sorts. It stores the strings to print in a StringBuffer and actually appends them to the TextArea when a flush() method is called. I used an AtomicBoolean to prevent the next generation from happening until the flush is complete, as it is done by a Platform.runLater() runnable. Also note that this solution is incredibly slow.
Not sure if I completely understand, but I think this may help.
Using Platform.runLater(...) is an appropriate approach for this.
The trick to avoiding flooding the FX Application Thread is to use an Atomic variable to store the value you're interested in. In the Platform.runLater method, retrieve it and set it to a sentinel value. From your background thread, update the Atomic variable, but only issue a new Platform.runLater if it's been set back to its sentinel value.
I figured this out by looking at the source code for Task. Have a look at how the updateMessage method (line 1131 at the time of writing) is implemented.
Here's an example which uses the same technique. This just has a (busy) background thread which counts as fast as it can, updating an IntegerProperty. An observer watches that property and updates an AtomicInteger with the new value. If the current value of the AtomicInteger is -1, it schedules a Platform.runLater.
In the Platform.runLater, I retrieve the value of the AtomicInteger and use it to update a Label, setting the value back to -1 in the process. This signals that I am ready for another UI update.
import java.text.NumberFormat;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class ConcurrentModel extends Application {
#Override
public void start(Stage primaryStage) {
final AtomicInteger count = new AtomicInteger(-1);
final AnchorPane root = new AnchorPane();
final Label label = new Label();
final Model model = new Model();
final NumberFormat formatter = NumberFormat.getIntegerInstance();
formatter.setGroupingUsed(true);
model.intProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(final ObservableValue<? extends Number> observable,
final Number oldValue, final Number newValue) {
if (count.getAndSet(newValue.intValue()) == -1) {
Platform.runLater(new Runnable() {
#Override
public void run() {
long value = count.getAndSet(-1);
label.setText(formatter.format(value));
}
});
}
}
});
final Button startButton = new Button("Start");
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
model.start();
}
});
AnchorPane.setTopAnchor(label, 10.0);
AnchorPane.setLeftAnchor(label, 10.0);
AnchorPane.setBottomAnchor(startButton, 10.0);
AnchorPane.setLeftAnchor(startButton, 10.0);
root.getChildren().addAll(label, startButton);
Scene scene = new Scene(root, 100, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public class Model extends Thread {
private IntegerProperty intProperty;
public Model() {
intProperty = new SimpleIntegerProperty(this, "int", 0);
setDaemon(true);
}
public int getInt() {
return intProperty.get();
}
public IntegerProperty intProperty() {
return intProperty;
}
#Override
public void run() {
while (true) {
intProperty.set(intProperty.get() + 1);
}
}
}
}
If you really want to "drive" the back end from the UI: that is throttle the speed of the backend implementation so you see all updates, consider using an AnimationTimer. An AnimationTimer has a handle(...) which is called once per frame render. So you could block the back-end implementation (for example by using a blocking queue) and release it once per invocation of the handle method. The handle(...) method is invoked on the FX Application Thread.
The handle(...) method takes a parameter which is a timestamp (in nanoseconds), so you can use that to slow the updates further, if once per frame is too fast.
For example:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
final BlockingQueue<String> messageQueue = new ArrayBlockingQueue<>(1);
TextArea console = new TextArea();
Button startButton = new Button("Start");
startButton.setOnAction(event -> {
MessageProducer producer = new MessageProducer(messageQueue);
Thread t = new Thread(producer);
t.setDaemon(true);
t.start();
});
final LongProperty lastUpdate = new SimpleLongProperty();
final long minUpdateInterval = 0 ; // nanoseconds. Set to higher number to slow output.
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
if (now - lastUpdate.get() > minUpdateInterval) {
final String message = messageQueue.poll();
if (message != null) {
console.appendText("\n" + message);
}
lastUpdate.set(now);
}
}
};
timer.start();
HBox controls = new HBox(5, startButton);
controls.setPadding(new Insets(10));
controls.setAlignment(Pos.CENTER);
BorderPane root = new BorderPane(console, null, null, controls, null);
Scene scene = new Scene(root,600,400);
primaryStage.setScene(scene);
primaryStage.show();
}
private static class MessageProducer implements Runnable {
private final BlockingQueue<String> messageQueue ;
public MessageProducer(BlockingQueue<String> messageQueue) {
this.messageQueue = messageQueue ;
}
#Override
public void run() {
long messageCount = 0 ;
try {
while (true) {
final String message = "Message " + (++messageCount);
messageQueue.put(message);
}
} catch (InterruptedException exc) {
System.out.println("Message producer interrupted: exiting.");
}
}
}
public static void main(String[] args) {
launch(args);
}
}
The best way to performing this is by usage of Task in JavaFx. This is be by far the best technique I've come across to update UI Controls in JavaFx.
Task task = new Task<Void>() {
#Override public Void call() {
static final int max = 1000000;
for (int i=1; i<=max; i++) {
updateProgress(i, max);
}
return null;
}
};
ProgressBar bar = new ProgressBar();
bar.progressProperty().bind(task.progressProperty());
new Thread(task).start();

Categories