Javafx live thread updates - java

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();

Related

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.

JavaFX animation restore to original state

My goal here is to have some animation on a node (such as a fade transition) that serves as a temporary notice that something is happening. I want the animation completely gone, like it never happened when that something has ended.
The code snipped below is an example of the problem I'm having. In the current state, when the button is hit to stop the process the button just stays at it's current opacity. If the commented line is uncommented, the button no longer stays at it's current opacity but updates to look correct. My problem then is that when the button is hit again, the CSS opacity for the default stylesheet (Modena.css for JavaFX 8) is no longer taking effect.
Is there something I'm doing wrong, or is there a better way altogether?
package gui.control.custom;
import javafx.animation.Animation;
import javafx.animation.FadeTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Test extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Stage stage = new Stage();
HBox box = new HBox();
streamButton = new Button("Start");
streamButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (started) {
stopProcess();
} else {
startProcess();
}
}
});
box.getChildren().add(streamButton);
stage.setScene(new Scene(box));
stage.show();
}
FadeTransition ft;
Button streamButton;
boolean started = false;
private void startProcess() {
streamButton.setDisable(true);
new Thread() {
#Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException ex) {
}
Platform.runLater(() -> {
started = true;
streamButton.setText("Stop");
streamButton.setDisable(false);
startButtonAnim();
});
}
}.start();
}
private void stopProcess() {
streamButton.setText("Start");
stopButtonAnim();
started = false;
}
private void startButtonAnim() {
ft = new FadeTransition(Duration.millis(500), streamButton);
ft.setFromValue(1.0);
ft.setToValue(0.3);
ft.setCycleCount(Animation.INDEFINITE);
ft.setAutoReverse(true);
ft.play();
}
private void stopButtonAnim() {
ft.stop();
//streamButton.setOpacity(1);
}
public static void main(String[] args) {
launch();
}
}
I think the best solution is to use jumpTo(Duration duration) right before you stop the Animation. Setting the duration to Duration.ZERO.
Circle circle2 = new Circle(250, 120, 80);
circle2.setFill(Color.RED);
circle2.setStroke(Color.BLACK);
FadeTransition fade = new FadeTransition();
fade.setDuration(Duration.millis(5000));
fade.setFromValue(10);
fade.setToValue(0.1);
fade.setCycleCount(1000);
fade.setAutoReverse(true);
fade.setNode(circle2);
fade.play();
Button btnStop = new Button("Stop");
btnStop.setOnAction((event) -> {
fade.jumpTo(Duration.ZERO);
fade.stop();
});
Another idea:
I have found this method in javadoc: getCurrentRate(),
which should give you negative result on reversing, so the code would look like this:
private void stopButtonAnim() {
while(ft.getCurrentRate>=0); //waiting till animation goes (skips if already reversing)
while(ft.getCurrentRate<=0); //and till reverse
ft.stop(); //then stop
streamButton.setOpacity(1); //make it 100% ;)
}
Maybe you have to add Thread.sleep(int) to while cycle
I would try this insetad of simply stop(); this line
setOnFinished(e->tryToStop());
And create this method as:
public void tryToStop(){
if(!started)
fm.stop();
}
stopProcess() method changes the started variable, so it will stop in this two cases:
if it is finished
AND
if it is reqested to stop
Not tested, just an idea

How to queue tasks in JavaFX?

I have made a GUI using JavaFX, and there are three radio buttons and once the user clicks submit and another thread is created and depending on what radiobutton was checked, the thread runs the required output and outputs the result to the console.
But while the thread is running (it takes around good 30 seconds for one process to complete) , I am able to check on any radiobutton. To it creates another thread and outputs long with the other ongoing thread. So my output box is just a jumble-wumble! I was looking at asynchronous task but I am not sure if that is something related to it.
Here is what I need: If a task is running, and I click on the submit button while it is running, wait for the previous task to END and THEN do the task.
Here is a psuedo code of my code
class TestMain {
//main
public void main(String ... args) {
launch(args);
}
/*declaring a new textfield with name m_status update here*/
/*once submit button is clicked*/{
//create a new thread
//to run
}
}
class ThreadBlahBlah implements Runnable {
if(/*first checkbox was selected*/){
//do these fancy stuff
Platform.runLater(new Runnable() {
#Override
public void run() {
TestMain.m_status_update.setText("Test Completed!");
}
});
}else if(/*second checkbox was selected*/){
//do these other fancy stuff
Platform.runLater(new Runnable() {
#Override
public void run() {
TestMain.m_status_update.setText("Test Completed!");
}
});
}
}
Please do not recommend me to disable radio buttons while the task is running cause I want to queue my tasks like a linked list.
Use a single-threaded executor to run your tasks:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
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.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class QueuedTaskExample extends Application {
private AtomicInteger taskCount = new AtomicInteger(0);
private ExecutorService exec = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r);
t.setDaemon(true); // allows app to exit if tasks are running
return t ;
});
// Use the following if you want the tasks to run concurrently, instead of consecutively:
// private ExecutorService exec = Executors.newCachedThreadPool(r -> {
// Thread t = new Thread(r);
// t.setDaemon(true);
// return t ;
// });
#Override
public void start(Stage primaryStage) {
// Just keep track of number of tasks pending/running for a status label:
IntegerProperty pendingTasks = new SimpleIntegerProperty(0);
Button startButton = new Button("Start");
TextArea textArea = new TextArea();
textArea.setEditable(true);
startButton.setOnAction(event -> {
Task<Void> task = createTask();
// add text to text area if task's message changes:
task.messageProperty().addListener((obs, oldMessage, newMessage) -> {
textArea.appendText(newMessage);
textArea.appendText("\n");
});
// for maintaining status label:
pendingTasks.set(pendingTasks.get()+1);
task.setOnSucceeded(taskEvent -> pendingTasks.set(pendingTasks.get()-1));
// run task in single-thread executor (will queue if another task is running):
exec.submit(task);
});
// layout etc
HBox controls = new HBox(startButton);
controls.setAlignment(Pos.CENTER);
controls.setPadding(new Insets(10));
Label statusLabel = new Label();
statusLabel.textProperty().bind(Bindings.format("Pending/running tasks: %s", pendingTasks));
BorderPane root = new BorderPane(textArea, statusLabel, null, controls, null);
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
#Override
public void stop() {
exec.shutdownNow();
}
// Trivial task that counts slowly to 5, updating its message as it goes:
private Task<Void> createTask() {
final int taskNumber = taskCount.incrementAndGet();
return new Task<Void>() {
#Override
public Void call() throws Exception {
for (int count=1; count<=5; count++) {
Thread.sleep(1000);
updateMessage("Task "+taskNumber+": Count "+count);
}
return null ;
}
};
}
public static void main(String[] args) {
launch(args);
}
}
When the user clicks on a radio button, first disable all radio buttons so the user will not be able to click on other radio buttons while your task is running.
When you finished with the background job, re-enable all radio buttons so the user can choose another task.
See Node.setDisabled() (RadioButton extends Node).
If you do need to queue tasks, your background thread should maintain a a task list, and when the user clicks, add the task to the list, which the background thread should consume (start another task if the current one is completed and there are more).
For advanced threaded execution see Executors and ExecutorService.

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();

javafx animation: displaying circles

I want to display 5 randomly positioned and colored circles. It was easy part. Now I want to adjust this code to be an animation. This application should generate random circles endlessly but the condition is that it should keep only last five circles on the screen. This is where I got stuck. JavaFx provides ListChangeListener. I think it is what I should use. But how?
The following is my unfinished code:
import java.util.Random;
import javafx.application.Application;
import javafx.collections.ListChangeListener;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class RandomColorTest extends Application {
int radius = 20;
int sceneWidth = 300;
int sceneHeight = 300;
private void init(Stage primaryStage) {
Group root = new Group();
primaryStage.setResizable(false);
primaryStage.setScene(new Scene(root, sceneWidth,sceneHeight));
for (int i = root.getChildren().size(); i < 5; i++) {
root.getChildren().add(createCircle());
// the following should convey the idea:
// if the collection holds 5 elements then keep least recently generated element for 1 second and then delete it
// add one new element
// if the collection holds 5 elements then keep least recently generated element for 1 second and then delete it
// add one new element
// and so on
root.getChildren().addListener(new ListChangeListener<E>() {
#Override
public void onChanged(
javafx.collections.ListChangeListener.Change<? extends E> arg0) {
// TODO Auto-generated method stub
}
});
}
}
// Create randomly positioned and colored circle
private Circle createCircle() {
final Circle circle = new Circle();
circle.setRadius(radius);
Random r = new Random();
int rCol1 = r.nextInt(256);
int rCol2 = r.nextInt(256);
int rCol3 = r.nextInt(256);
int rX = radius+r.nextInt(sceneWidth);
if (rX>sceneWidth-radius) {
rX=rX-2*radius;
}
int rY = radius+r.nextInt(sceneHeight);
if (rY>sceneHeight-radius) {
rY=rY-2*radius;
}
circle.setLayoutX(rX);
circle.setLayoutY(rY);
circle.setStroke(Color.BLACK);
circle.setFill(Color.rgb(rCol1,rCol2,rCol3));
System.out.println(rCol1+"-"+rCol2+"-"+rCol3+"-"+rX+"-"+rY);
return circle;
}
#Override public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}
After having managed to make ListChangeListener compile without errors it doesn't still work the way expected. Changes made to for loop:
for (int i = root.getChildren().size();;i++) {
final ObservableList<Node> ol = root.getChildren();
// the following should convey the idea:
// if the collection holds 5 elements then keep least recently generated element for 1 second and then delete it
// add one new element
// if the collection holds 5 elements then keep least recently generated element for 1 second and then delete it
// add one new element
// and so on
ol.add(createCircle());
ol.addListener( new ListChangeListener<Node>(){
#Override
public void onChanged(
javafx.collections.ListChangeListener.Change<? extends Node> arg0) {
// TODO Auto-generated method stub
System.out.println("one new element added, size:"+ol.size());
if (ol.size()==5) {
ol.remove(0);
}
}
});
}
For loop is defined to loop infinitely (probably not the right way to solve this problem also) and I can see from console that circles are removed and added during the program run. Alas, I can't see GUI anymore.
A similar question was also asked on the on the Oracle forums last year.
Here is sample solution using Timeline, which I prefer to a solution relying on worker threading. Though both can get the job done, I find using the JavaFX animation APIs more elegant and less error prone.
import javafx.animation.*;
import javafx.application.Application;
import javafx.event.*;
import javafx.scene.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.Random;
public class FiveAutoCircleExample extends Application {
private static final Random r = new Random();
public static final int SCENE_SIZE = 800;
public static void main(String[] args) throws Exception { launch(args); }
public void start(final Stage stage) throws Exception {
final Group circles = new Group();
final Timeline animation = new Timeline(
new KeyFrame(Duration.seconds(.5),
new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent actionEvent) {
while (circles.getChildren().size() >= 5) circles.getChildren().remove(0);
int radius = 10 * r.nextInt(20);
circles.getChildren().add(
new Circle(
r.nextInt(SCENE_SIZE - radius * 2) + radius, r.nextInt(SCENE_SIZE - radius * 2) + radius,
radius,
new Color(r.nextDouble(), r.nextDouble(), r.nextDouble(), r.nextDouble())
)
);
}
})
);
animation.setCycleCount(Animation.INDEFINITE);
animation.play();
// display the scene.
stage.setScene(new Scene(circles, SCENE_SIZE, SCENE_SIZE, Color.CORNSILK));
stage.show();
}
}
In your code, you have some mistakes:
the GUI is not shown because, the execution flow never reaches the primaryStage.show(); due to infinite loop in the init(primaryStage);.
new ListChangeListener is added again and again in a loop. However you should add it only once in normal situations.
You are manipulating the ol (ol.remove(0);) in its own listener which triggers the new change event recursively.
As a solution: periodic tasks, long-time background executions can be separated to a different thread.
#Override
public void start(Stage primaryStage) throws Exception {
Group root = new Group();
primaryStage.setResizable(false);
primaryStage.setScene(new Scene(root, sceneWidth, sceneHeight));
final ObservableList<Node> ol = root.getChildren();
new Thread(new Runnable() {
#Override public void run() {
while (true) {
try {
// Wait for 2 seconds.
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
Platform.runLater(new Runnable() {
#Override public void run() {
System.out.println("ol size:" + ol.size());
if (ol.size() == 5) {
ol.remove(0);
}
ol.add(createCircle());
}
});
}
}
}).start();
primaryStage.show();
}
I have only changed the content of start(Stage primaryStage). There is no need to add a listener. This solution is very quick but not elegant way. You must manage the thread yourself. For more elegant and modern approach refer to Worker Threading in JavaFX 2.0.
In addition, if you really want a real animation then see the example Colorful Circles Application.

Categories