How to get a scheduled event to interact with JavaFX GUI - java

I'm a beginner Java programmer trying to figure this out. I have a piece of code that does some calculation and updates a label in my JavaFX GUI. It runs every 100ms using a ScheduledExecutorService and a Runnable. The problem is it cannot update the Label of the GUI. I have spent yesterday looking for a way to do it and most of the topics seem to be solved with the use of Platform.runLater but even putting my code into the runLater runnable seems to still not work. Another thing I have found is using the Java concurrency framework, but I don't know how to use that for a repeating scheduled service like this. Here's how I wrote the code:
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable loop = new Runnable() {
public void run() {
Platform.runLater(new Runnable() {
#Override public void run() {
double result = calculation();
labelResult.setText("" + result);
}
});
}
};
executor.scheduleAtFixedRate(loop, 0, 100, TimeUnit.MILLISECONDS);
How could I do this?
EDIT:
I'm including a full example.
Main class:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javafx.application.Application;
import javafx.application.Platform;
public class Main{
private static long value = 0;
private static Gui gui;
public static void main(String[] args){
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable loop = new Runnable() {
public void run() {
Platform.runLater(new Runnable() {
#Override public void run() {
calculate();
}
});
}
};
executor.scheduleAtFixedRate(loop, 0, 100, TimeUnit.MILLISECONDS);
Application.launch(Gui.class, args);
}
public static void calculate(){
double result = value++;
gui.setResult(result);
}
public static void setGui(Gui ref){
gui = ref;
}
}
Gui class:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Gui extends Application{
private Stage window;
private Scene scene;
private HBox layout = new HBox();
private Label result = new Label("TEST");
#Override
public void start(Stage stage) throws Exception {
window = stage;
layout.getChildren().addAll(result);
Main.setGui(this);
scene = new Scene(layout, 1280, 720);
window.setTitle("Example");
window.setResizable(false);
window.setScene(scene);
window.show();
}
public void setResult(double res){
result.setText("" + res);
}
}

The overall structure of your application is wrong. The reason that your scheduled executor service is failing is that you attempt to start it before you launch the JavaFX application, and consequently your first call to Platform.runLater(...) happens before the FX toolkit has been started and before the FX Application Thread is running.
If you wrap the call to Platform.runLater() in a try block and catch the exception:
Runnable loop = new Runnable() {
public void run() {
try {
Platform.runLater(new Runnable() {
#Override
public void run() {
calculate();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
};
you will see the exception:
java.lang.IllegalStateException: Toolkit not initialized
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:273)
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:268)
at javafx.application.Platform.runLater(Platform.java:83)
at Main$1.run(Main.java:17)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
(Incidentally, handling the exception will also allow the executor to continue, so eventually it will "recover" as the toolkit will be started at some point. You may also see other exceptions, because, e.g. there are race conditions on the gui field: some iterations of the executor may get called before gui is initialized.)
You should think of the Application.start() method essentially as the entry point for the application. When you call launch() (or when it is called for you, which happens in most final deployment scenarios), the FX Toolkit is started, then an instance of the Application subclass is created, and start() is invoked on that instance on the FX Application Thread.
So the way to structure this is to drive it all from the start() method. Create an instance of your GUI class there, create an instance of the class that is running your scheduled executor, tie them together, and then just display the UI in the provided stage. Here's one possible example of this refactoring:
Main.java:
import javafx.application.Application;
import javafx.stage.Stage;
public class Main extends Application{
private Stage window;
#Override
public void start(Stage stage) throws Exception {
window = stage;
Gui gui = new Gui();
UpdateService service = new UpdateService(gui);
service.startService();
window.setTitle("Example");
window.setResizable(false);
window.setScene(gui.getScene());
window.show();
}
public static void main(String[] args) {
launch(args);
}
}
UpdateService.java:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javafx.application.Platform;
public class UpdateService {
private long value = 0;
private final Gui gui;
public UpdateService(Gui gui) {
this.gui = gui;
}
public void startService() {
// create executor that uses daemon threads;
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t;
});
Runnable loop = new Runnable() {
public void run() {
Platform.runLater(new Runnable() {
#Override
public void run() {
calculate();
}
});
}
};
executor.scheduleAtFixedRate(loop, 0, 100, TimeUnit.MILLISECONDS);
}
public void calculate() {
double result = value++;
gui.setResult(result);
}
}
Gui.java:
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
public class Gui {
private Scene scene;
private HBox layout = new HBox();
private Label result = new Label("TEST");
public Gui() {
layout.getChildren().addAll(result);
scene = new Scene(layout, 1280, 720);
}
public Scene getScene() {
return scene ;
}
public void setResult(double res){
result.setText("" + res);
}
}
Finally, note that a cleaner way to get regularly-repeating functionality that runs on the FX Application Thread is to use the Animation API (as in JavaFX periodic background task):
public void startService() {
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(100), e -> calculate()));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
}

Related

Non-blocking signaling between threads

I have a JavaFX app that runs two threads at startup. One is the UI thread that must not be blocked. The other is a thread that prepares a large table (it takes about 20 seconds). I want to signal the UI thread when the second thread is done, so it can change the color of a rectangle from red to green. I have tried solutions using the synchronized keyword, but they all caused the UI thread to be blocked.
I used the following resources to obtain the below code.
Concurrency in JavaFX
Execute task in background in JavaFX
The below app simply displays a red rectangle which, after five seconds, turns to green. Explanations after the code.
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.concurrent.Worker.State;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class JfxTask0 extends Application {
private Task<Void> task;
#Override
public void init() throws Exception {
task = new Task<Void>() {
#Override
protected Void call() throws Exception {
try {
Thread.sleep(5000L);
}
catch (InterruptedException xInterrupted) {
if (isCancelled()) {
System.out.println("CANCELLED!");
}
}
return null;
}
};
}
#Override
public void start(Stage primaryStage) throws Exception {
Rectangle rect = new Rectangle(25.0d, 25.0d, 50.0d, 50.0d);
rect.setFill(Color.RED);
task.stateProperty().addListener(new ChangeListener<Worker.State>() {
#Override
public void changed(ObservableValue<? extends State> workerStateProperty,
Worker.State oldValue,
Worker.State newValue) {
if (newValue == Worker.State.SUCCEEDED) {
rect.setFill(Color.GREEN);
}
}
});
new Thread(task).start();
Group root = new Group();
ObservableList<Node> children = root.getChildren();
children.add(rect);
Scene scene = new Scene(root, 100.0D, 100.0D);
primaryStage.setScene(scene);
primaryStage.setTitle("Task");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Method init() is declared in class javafx.application.Application. It is executed before method start() and, as its name suggests, is used to initialize the JavaFX application. In this method I create the background task. The background task merely sleeps for five seconds.
In method start() I create the red rectangle and then launch the background task but before launching the task, I register a listener with one of the task's properties. This property will be set to a particular value once the task completes.
After the task is launched, I build the rest of the GUI and display it.
Once the task terminates, then listener is invoked and it sets the rectangle color to green.
You can use a handler for this problem.
there is example
Add this in your main activity and create handler.
Handler h = new Handler(){
#Override public void handleMessage(Message msg){
switch(msg.what){
case 1:
// what you want when complete
break;
default:
break;
}
}
}
MyThread thread = new MyThread(new Messenger(h));
thread.start();
Now add this in your thread file.
public class MyThread{
Messenger m;
public MyThread(Messenger m){
this.m = m;
}
public void run(){
super.run();
// your codes
//
//when your task complete
Message msg = Message.obtain();
msg.what = 1;
msg.obj = "";
try{
m.send(msg);
}catch(IOException e){
e.printStackTrace();
}
}
}

JavaFX Application Thread to Task Communication

I've been learning about JavaFX's Tasks and using these to communicate with the Application thread using Platform.runLater or the task's updateValue method etc. However, my Task needs to know when a user presses a button on the GUI as this could change the value needed to be returned by the Task's updateValue method. How do I go about doing this? I know how to respond to button press events on single threaded applications but am not sure how do deal with it in a thread-safe manner.
Update:
This is what I have so far, is this a sensible way of implementing the button event?
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.PixelFormat;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.control.Button;
import java.nio.IntBuffer;
public class TaskExample extends Application {
private Canvas canvas;
private PixelWriter pixel_writer;
#Override
public void start(Stage primaryStage) throws Exception {
canvas = new Canvas(256, 256);
pixel_writer = canvas.getGraphicsContext2D().getPixelWriter();
MyTask task = new MyTask();
task.valueProperty().addListener((c) -> {
if(task.getValue() != null) {
update(task.getValue());
}
});
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
Button button = new Button("Button 1");
// On the button click event it calls the eventFired() method
button.setOnAction((event) -> {
task.eventFired();
});
Pane pane = new VBox();
pane.getChildren().addAll(canvas, button);
primaryStage.setScene(new Scene(pane));
primaryStage.show();
}
public void update(IntBuffer data) {
pixel_writer.setPixels(
0,
0,
256,
256,
PixelFormat.getIntArgbInstance(),
data,
256
);
}
public static void main(String[] args) {
launch(args);
}
class MyTask extends Task<IntBuffer> {
public void eventFired() {
System.out.println("Event fired");
}
public void update(IntBuffer data) {
updateValue(data);
}
#Override
protected IntBuffer call() throws InterruptedException {
while(true) {
for (int i=0; i<3; i++) {
Thread.sleep(1000);
IntBuffer data = IntBuffer.allocate(256*256);
for(int j=0; j<256*256; j++) {
switch(i) {
case 0: data.put(0xFF0000FF); break;
case 1: data.put(0xFF00FF00); break;
case 2: data.put(0xFFFF0000); break;
}
}
data.rewind();
update(data);
}
}
}
}
}
What I would do here is to think about ways to refactor what you are doing to avoid communication between two different threads. For example, instead of thinking of what you are doing as one long-running task that updates the UI as it progresses, think of it as a series of individual tasks that each update the UI when they complete. The ScheduledService class provides the machinery to manage these tasks and communicate between them and the FX Application Thread in a clean and safe way:
import java.nio.IntBuffer;
import java.util.Arrays;
import javafx.application.Application;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.Button;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelWriter;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TaskExample extends Application {
private Canvas canvas;
private PixelWriter pixel_writer;
#Override
public void start(Stage primaryStage) throws Exception {
canvas = new Canvas(256, 256);
pixel_writer = canvas.getGraphicsContext2D().getPixelWriter();
MyService service = new MyService();
service.setPeriod(Duration.seconds(1));
service.valueProperty().addListener((ols, oldData, newData) -> {
if(newData != null) {
update(newData);
}
});
service.start();
Button button = new Button("Button 1");
Pane pane = new VBox();
pane.getChildren().addAll(canvas, button);
primaryStage.setScene(new Scene(pane));
primaryStage.show();
}
public void update(IntBuffer data) {
pixel_writer.setPixels(
0,
0,
256,
256,
PixelFormat.getIntArgbInstance(),
data,
256
);
}
public static void main(String[] args) {
launch(args);
}
class MyService extends ScheduledService<IntBuffer> {
// both instance variables accessed only on FX Application Thread:
private final int[] colors = {0xFF0000FF, 0xFF00FF00, 0xFFFF0000} ;
private int count = -1 ;
#Override
protected Task<IntBuffer> createTask() {
// invoked on FX Application Thread
count = (count + 1) % colors.length ;
return new MyTask(colors[count]);
}
}
class MyTask extends Task<IntBuffer> {
private final int color ;
MyTask(int color) {
// invoked on FX Application Thread:
this.color = color ;
}
#Override
protected IntBuffer call() {
// invoked on background thread:
IntBuffer data = IntBuffer.allocate(256*256);
int[] a = new int[256*256];
Arrays.fill(a, color);
data.put(a, 0, a.length);
data.rewind();
return data ;
}
}
}
You haven't been very specific about how the UI is supposed to interact with the background thread, but if you wanted to change the behavior of the service when the button is pressed, you would now be changing the behavior of the createTask method, which is invoked on the FX Application Thread, instead of changing the behavior of a method already running on a different thread. This avoids any "low-level" concerns about synchronization.
For example:
class MyService extends ScheduledService<IntBuffer> {
// all instance variables accessed only on FX Application Thread:
private final int[][] colors = {
{0xFF0000FF, 0xFF00FF00, 0xFFFF0000},
{0xFF00FFFF, 0xFFFF00FF, 0xFFFFFF00}
};
private int count = -1 ;
private int scheme = 0 ;
#Override
protected Task<IntBuffer> createTask() {
// invoked on FX Application Thread
count = (count + 1) % colors[scheme].length ;
return new MyTask(colors[scheme][count]);
}
public void changeScheme() {
// invoked on FX Application Thread
scheme = (scheme + 1) % colors.length ;
}
}
and then just
button.setOnAction(e -> service.changeScheme());
Adding a call to service.restart(); here will force the change to happen as soon as possible:
button.setOnAction(e -> {
service.changeScheme();
service.restart();
});
There is pretty much always a way to refactor your code to take advantage of the library classes like this to avoid low-level communication between threads.

Showing the Progress of a Background Task

Hi i want to show the progress of a background task.
#FXML
private void addOfficeBarrierSelect() {
App.getInstance().showProgressIndicator(myController);
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
ScreensController colllectScreenController = new ScreensController();
colllectScreenController.loadScreen(Screens.ADD_OFFICE_BARRIER);
colllectScreenController.setScreen(Screens.ADD_OFFICE_BARRIER);
content.setContent(null);
content.setContent(colllectScreenController);
return null;
}
#Override
protected void succeeded() {
App.getInstance().hideProgressIndicator(myController);
}
#Override
protected void failed() {
super.failed();
App.getInstance().hideProgressIndicator(myController);
}
};
new Thread(task).start();
}
And below code is the progress popup i have used in my application.
public void showProgressIndicator(ScreensController myController) {
myController.setDisable(true);
if (popupProgressIndicator == null) {
JFXSpinner spinner = new JFXSpinner();
spinner.setLayoutX(0);
spinner.setLayoutY(0);
spinner.setPrefHeight(200);
spinner.setPrefWidth(200);
popupProgressIndicator = new Popup();
popupProgressIndicator.getContent().add(spinner);
}
if (!popupProgressIndicator.isShowing()) {
popupProgressIndicator.show(primaryStage);
popupProgressIndicator.centerOnScreen();
}
}
public void hideProgressIndicator(ScreensController myController) {
myController.setDisable(false);
if (popupProgressIndicator.isShowing())
popupProgressIndicator.hide();
}
The problem is when i run the app , No FX app thread exception occuring..
Exception in thread "Thread-6" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-6 and pointing at void call() method inside Task thread.
Is there any solution for it..
James_D solution Actually works for me.But i got to change the code to this.
showProgressIndicator(myController);
Task<FXMLLoader> task = new Task<FXMLLoader>() {
#Override
protected FXMLLoader call() throws Exception {
FXMLLoader loader = new FXMLLoader(App.class.getResource(Screens.ADD_MEMBER));
return loader;
}
#Override
protected void succeeded() {
super.succeeded();
hideProgressIndicator(myController);
}
#Override
protected void failed() {
super.failed();
hideProgressIndicator(myController);
}
};
new Thread(task).start();
After changing the code again progress not showing.just followed #james_D anwser.
You are getting the exception because you are updating the UI from a background thread. You need to update the UI from the FX Application Thread. You can do this with a call to Platform.runLater() if you need to make incremental updates during your call() method execution, or (as it seems in this case: I'm not sure exactly what the loadScreen and setScreen methods do, but I am assuming they do not change the existing scene graph) if you only need to update the UI once the task is complete, update the UI in the succeeded method of the task:
#FXML
private void addOfficeBarrierSelect() {
App.getInstance().showProgressIndicator(myController);
Task<ScreensController> task = new Task<Void>() {
#Override
protected ScreensController call() throws Exception {
ScreensController colllectScreenController = new ScreensController();
colllectScreenController.loadScreen(Screens.ADD_OFFICE_BARRIER);
colllectScreenController.setScreen(Screens.ADD_OFFICE_BARRIER);
return colllectScreenController;
}
#Override
protected void succeeded() {
super.succeeded();
content.setContent(getValue());
App.getInstance().hideProgressIndicator(myController);
}
#Override
protected void failed() {
super.failed();
App.getInstance().hideProgressIndicator(myController);
}
};
new Thread(task).start();
}
Here's a simple example to illustrate how it could be done, using an AnimationTimer and some state (busy and labelText). The state of the button and the label are amended on every tick of the animation timer. And the task is the thing that updates the state (NOT the UI).
The state here would require to be synchronized, of course, so you'd perhaps wish to implement a postMessage() method that would allow the task to report state back to the UI, which would then add to a message queue, which would then in turn be consumed by the AnimationTimer. However, the code provided below is for ILLUSTRATIVE PURPOSES ONLY. The point is to highlight that you cannot update UI controls from a non-UI thread.
I've also added a ProgressBar and bound its progressProperty to the progressProperty of the task. So, there's TWO ways of managing updates for you :)
Using progressProperty() binding is probably the best here. No need for the AnimationTimer in that instance. But, if you do implement that way of doing it, then you should implement a message queue on the UI thread and synchronize state change via a postMessage() method.
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Progress extends Application {
String labelText = "Foobar";
boolean busy = false;
#Override
public void start(Stage stage) {
Label label = new Label();
ProgressBar progress = new ProgressBar();
progress.setProgress(0);
Button button = new Button("Test");
button.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
System.out.println("Button has been pressed...");
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
busy = true;
System.out.println("Calling long running task...");
for (int i = 0; i <= 100; i++) {
labelText = "Progress: " + i;
updateProgress(i, 100);
Thread.sleep(50);
}
return null;
}
#Override
protected void succeeded() {
System.out.println("Succeeded");
busy = false;
}
#Override
protected void failed() {
System.out.println("Failed");
}
};
progress.progressProperty().bind(task.progressProperty());
Thread thread = new Thread(task);
thread.start();
}
});
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
label.setText(labelText);
button.setDisable(busy);
}
};
timer.start();
VBox vbox = new VBox(label, progress, button);
Scene scene = new Scene(vbox, 400, 300);
stage.setTitle("Test");
stage.setScene(scene);
stage.show();
}
}
NB the task must do nothing with controls, only state.
See also here: https://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm
And this SO question: JavaFX : How to pass value from background thread to JavaFX UI thread with Task.

Javafx Toolkit initialisation on Font loading [duplicate]

I want to create basic JUnit test for JavaFX 8 application. I have this simple code sample:
public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Tabs");
Group root = new Group();
Scene scene = new Scene(root, 400, 250, Color.WHITE);
TabPane tabPane = new TabPane();
BorderPane borderPane = new BorderPane();
for (int i = 0; i < 5; i++) {
Tab tab = new Tab();
tab.setText("Tab" + i);
HBox hbox = new HBox();
hbox.getChildren().add(new Label("Tab" + i));
hbox.setAlignment(Pos.CENTER);
tab.setContent(hbox);
tabPane.getTabs().add(tab);
}
// bind to take available space
borderPane.prefHeightProperty().bind(scene.heightProperty());
borderPane.prefWidthProperty().bind(scene.widthProperty());
borderPane.setCenter(tabPane);
root.getChildren().add(borderPane);
primaryStage.setScene(scene);
primaryStage.show();
}
}
I only have this code so far:
import javafx.application.Application;
import javafx.stage.Stage;
import org.junit.BeforeClass;
public class BasicStart extends Application {
#BeforeClass
public static void initJFX() {
Thread t = new Thread("JavaFX Init Thread") {
#Override
public void run() {
Application.launch(BasicStart.class, new String[0]);
}
};
t.setDaemon(true);
t.start();
}
#Override
public void start(Stage primaryStage) throws Exception {
// noop
}
}
Can you tell me how I can create JUnit test for the above code?
I use a Junit Rule to run unit tests on the JavaFX thread. The details are in this post. Just copy the class from that post and then add this field to your unit tests.
#Rule public JavaFXThreadingRule javafxRule = new JavaFXThreadingRule();
This code works for both JavaFX 2 and JavaFX 8.
The easiest aproach is the following:
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.stage.Stage;
import org.junit.Test;
public class BasicStart {
#Test
public void testA() throws InterruptedException {
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
new JFXPanel(); // Initializes the JavaFx Platform
Platform.runLater(new Runnable() {
#Override
public void run() {
new Main().start(new Stage()); // Create and
// initialize
// your app.
}
});
}
});
thread.start();// Initialize the thread
Thread.sleep(10000); // Time to use the app, with out this, the thread
// will be killed before you can tell.
}
}
Hope it helps!
Based on Brian Blonski 's answer I created a JUnit-Testrunner, that does essentially the same thing, but is a bit simpler to use in my opinion.
Using it, your test would look like this:
#RunWith( JfxTestRunner.class )
public class MyUnitTest
{
#Test
public void testMyMethod()
{
//...
}
}

How to update the label box every 2 seconds in java fx?

I'm trying to simulate a basic thermostat in an application GUI.
I want to update a label box value every 2 secs with the new temperature value.
For example, my intial temperature will be displayed as 68 degrees and updated to 69, to 70, etc. till 75 every 2 seconds.
This is a piece of code I wrote in Java fx. controlpanel is object of te form where the label box is present. It updates only the final value as 75. It doesnt update it every 2 secs. I have written a method pause to cause a 2 secs delay. All labels are updated with their final values but not updated every 2 secs. When I debug, I can see that the values are increased by one every 2 secs. This code is written in button onClick event
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
int i=0;
Timer asd = new Timer(1000,null);
asd.setDelay(1000);
while(i < 10)
{
jTextField1.setText(Integer.toString(i));
i++;
asd.start();
}
}
To solve your task using Timer you need to implement TimerTask with your code and use Timer#scheduleAtFixedRate method to run that code repeatedly:
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
System.out.print("I would be called every 2 seconds");
}
}, 0, 2000);
Also note that calling any UI operations must be done on Swing UI thread (or FX UI thread if you are using JavaFX):
private int i = 0;
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
jTextField1.setText(Integer.toString(i++));
}
});
}
}, 0, 2000);
}
In case of JavaFX you need to update FX controls on "FX UI thread" instead of Swing one. To achieve that use javafx.application.Platform#runLater method instead of SwingUtilities
Here is an alternate solution which uses a JavaFX animation Timeline instead of a Timer.
I like this solution because the animation framework ensures that everything happens on the JavaFX application thread, so you don't need to worry about threading issues.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.event.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.Random;
public class ThermostatApp extends Application {
#Override public void start(final Stage stage) throws Exception {
final Thermostat thermostat = new Thermostat();
final TemperatureLabel temperatureLabel = new TemperatureLabel(thermostat);
VBox layout = new VBox(10);
layout.getChildren().addAll(temperatureLabel);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 20; -fx-font-size: 20;");
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
class TemperatureLabel extends Label {
public TemperatureLabel(final Thermostat thermostat) {
textProperty().bind(
Bindings.format(
"%3d \u00B0F",
thermostat.temperatureProperty()
)
);
}
}
class Thermostat {
private static final Duration PROBE_FREQUENCY = Duration.seconds(2);
private final ReadOnlyIntegerWrapper temperature;
private final TemperatureProbe probe;
private final Timeline timeline;
public ReadOnlyIntegerProperty temperatureProperty() {
return temperature.getReadOnlyProperty();
}
public Thermostat() {
probe = new TemperatureProbe();
temperature = new ReadOnlyIntegerWrapper(probe.readTemperature());
timeline = new Timeline(
new KeyFrame(
Duration.ZERO,
new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent actionEvent) {
temperature.set(probe.readTemperature());
}
}
),
new KeyFrame(
PROBE_FREQUENCY
)
);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
}
class TemperatureProbe {
private static final Random random = new Random();
public int readTemperature() {
return 72 + random.nextInt(6);
}
}
The solution is based upon the countdown timer solution from: JavaFX: How to bind two values?
Calling Platform.runLater worked for me:
Platform.runLater(new Runnable() {
#Override
public void run() {
}
});

Categories

Resources