Multiple independent stages in JavaFX - javafx

Is there a way to launch multiple independent stages in JavaFX? By independent I mean that the stages are all created from the main thread.
At the moment my application is more or less an algorithm where I would like to plot some charts and tables during execution (mainly to check whether the results are correct/ to debug).
The problem is that I cannot figure out how to create and show multiple stages independently, i.e. I would like to do something like this
public static void main(){
double[] x = subfunction.dosomething();
PlotUtil.plot(x); //creates a new window and shows some chart/table etc.
double[] y = subfunction.dosomethingelse();
PlotUtil.plot(y); //creates a new window and shows some chart/table etc.
.....
}
which would allow to use PlotUtil as one would use the plotting functions in other scripting languages (like Matlab or R).
So the main question is how to "design" PlotUtils? So far I tried two things
PlotUtils uses Application.launch for each plot call (creating a new stage with a single scene every time) --> does not work as Application.launch can only be invoked once.
Create some kind of "Main Stage" during the first call to PlotUtils, get a reference to the created Application and start subsequent stages from there --> does not work as using Application.launch(SomeClass.class) I am not able to get a reference to the created Application instance.
What kind structure/design would allow me to implement such a PlotUtils function?
Update 1:
I came up with the following idea and was wondering whether there are any major mistakes in this solution.
Interface to be implemented by all "Plots"
public abstract class QPMApplication implements StageCreator {
#Override
public abstract Stage createStage();
}
Plotting functionality:
public class PlotStage {
public static boolean toolkitInialized = false;
public static void plotStage(String title, QPMApplication stageCreator) {
if (!toolkitInialized) {
Thread appThread = new Thread(new Runnable() {
#Override
public void run() {
Application.launch(InitApp.class);
}
});
appThread.start();
}
while (!toolkitInialized) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Platform.runLater(new Runnable() {
#Override
public void run() {
Stage stage = stageCreator.createStage();
stage.show();
}
});
}
public static class InitApp extends Application {
#Override
public void start(final Stage primaryStage) {
toolkitInialized = true;
}
}
}
Using it:
public class PlotStageTest {
public static void main(String[] args) {
QPMApplication qpm1 = new QPMApplication() {
#Override
public Stage createStage() {
Stage stage = new Stage();
StackPane root = new StackPane();
Label label1 = new Label("Label1");
root.getChildren().add(label1);
Scene scene = new Scene(root, 300, 300);
stage.setTitle("First Stage");
stage.setScene(scene);
return stage;
}
};
PlotStage.plotStage(qpm1);
QPMApplication qpm2 = new QPMApplication() {
#Override
public Stage createStage() {
Stage stage = new Stage();
StackPane root = new StackPane();
Label label1 = new Label("Label2");
root.getChildren().add(label1);
Scene scene = new Scene(root, 300, 200);
stage.setTitle("Second Stage");
stage.setScene(scene);
return stage;
}
};
PlotStage.plotStage(qpm2);
System.out.println("Done");
}
}

The easiest approach here would be just to refactor your application so that it is driven from the FX Application thread. For example, you could rewrite your original code block as
public class Main extends Application {
#Override
public void start(Stage primaryStageIgnored) {
double[] x = subfunction.dosomething();
PlotUtil.plot(x); //creates a new window and shows some chart/table etc.
double[] y = subfunction.dosomethingelse();
PlotUtil.plot(y); //creates a new window and shows some chart/table etc.
// .....
}
public static void main(String[] args) {
launch(args);
}
}
Now PlotUtil.plot(...) merely creates a Stage, puts a Scene in it, and show()s it.
This assumes the methods you're calling don't block, but if they do you just have to wrap them in a Task and call PlotUtils.plot(...) in the onSucceeded handler for the task.
If you really want to drive this from a non-JavaFX application, there's a fairly well-known hack to force the JavaFX Application thread to start if it's not already started, by creating a new JFXPanel. A JFXPanel should be created on the AWT event dispatch thread.
Here's a very basic example of the second technique. Start the application and type "show" into the console. (Type "exit" to exit.)
import java.util.Scanner;
import java.util.concurrent.FutureTask;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javax.swing.SwingUtilities;
public class Main {
private JFXPanel jfxPanel ;
public void run() throws Exception {
boolean done = false ;
try (Scanner scanner = new Scanner(System.in)) {
while (! done) {
System.out.println("Waiting for command...");
String command = scanner.nextLine();
System.out.println("Got command: "+command);
switch (command.toLowerCase()) {
case "exit":
done = true;
break ;
case "show":
showWindow();
break;
default:
System.out.println("Unknown command: commands are \"show\" or \"exit\"");
}
}
Platform.exit();
}
}
private void showWindow() throws Exception {
ensureFXApplicationThreadRunning();
Platform.runLater(this::_showWindow);
}
private void _showWindow() {
Stage stage = new Stage();
Button button = new Button("OK");
button.setOnAction(e -> stage.hide());
Scene scene = new Scene(new StackPane(button), 350, 75);
stage.setScene(scene);
stage.show();
stage.toFront();
}
private void ensureFXApplicationThreadRunning() throws Exception {
if (jfxPanel != null) return ;
FutureTask<JFXPanel> fxThreadStarter = new FutureTask<>(() -> {
return new JFXPanel();
});
SwingUtilities.invokeLater(fxThreadStarter);
jfxPanel = fxThreadStarter.get();
}
public static void main(String[] args) throws Exception {
Platform.setImplicitExit(false);
System.out.println("Starting Main....");
new Main().run();
}
}
Here is something more along the lines I would actually follow, if I wanted the user to interact via the OS terminal (i.e. using System.in). This uses the first technique, where the application is driven by an FX Application subclass. Here I create two background threads, one to read commands from System.in, and one to process them, passing them via a BlockingQueue. Even though nothing is displayed in the main FX Application Thread, it is still a very bad idea to block that thread waiting for commands. While the threading adds a small level of complexity, this avoids the "JFXPanel" hack, and doesn't rely on there being an AWT implementation present.
import java.util.Scanner;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class FXDriver extends Application {
BlockingQueue<String> commands ;
ExecutorService exec ;
#Override
public void start(Stage primaryStage) throws Exception {
exec = Executors.newCachedThreadPool(runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t ;
});
commands = new LinkedBlockingQueue<>();
Callable<Void> commandReadThread = () -> {
try (Scanner scanner = new Scanner(System.in)) {
while (true) {
System.out.print("Enter command: ");
commands.put(scanner.nextLine());
}
}
};
Callable<Void> commandProcessingThread = () -> {
while (true) {
processCommand(commands.take());
}
};
Platform.setImplicitExit(false);
exec.submit(commandReadThread);
exec.submit(commandProcessingThread);
}
private void processCommand(String command) {
switch (command.toLowerCase()) {
case "exit":
Platform.exit();
break ;
case "show":
Platform.runLater(this::showWindow);
break;
default:
System.out.println("Unknown command: commands are \"show\" or \"exit\"");
}
}
#Override
public void stop() {
exec.shutdown();
}
private void showWindow() {
Stage stage = new Stage();
Button button = new Button("OK");
button.setOnAction(e -> stage.hide());
Scene scene = new Scene(new StackPane(button), 350, 75);
stage.setScene(scene);
stage.show();
stage.toFront();
}
public static void main(String[] args) {
launch(args);
}
}

Related

Sound stops working after pressing a key 53 times

I'm creating a game in JavaFX (something like Space Invaders) and I'm having problems with the shooting sound, particularly when I press a key quite a few times not only the sound stops being played but other sounds also stop working.
I've done some small research and it seems that this kind of problem is fairly popular and it involves releasing the MediaPlayer object/instance but I can't call that method(?).
I've tried using dispose() method but it disables the shot sound completely.
I have two classes, GameApp:
primaryStage.getScene().setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.SPACE && playable) {
Audio.playPlayerShotSound();
}
}
});
and Audio:
public class Audio {
private static Media shot = new Media(new File("resources/playerShot.wav").toURI().toString());
public static void playPlayerShotSound() {
MediaPlayer shotSound = new MediaPlayer(shot);
shotSound.setVolume(0.2);
shotSound.play();
}
I've created another class using JavaFX and the sound stops after pressing Space 64 times.
package examples;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.stage.Stage;
import javafx.scene.layout.Pane;
import java.io.File;
public class GameInst extends Application {
Pane root;
private AnimationTimer timer;
private static Media shot = new Media(new File("resources/playerShot.wav").toURI().toString());
int count = 0;
private Parent createContent() {
root = new Pane();
root.setPrefSize(500, 500);
timer = new AnimationTimer() {
#Override
public void handle(long now) {
onUpdate();
}
};
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
onUpdate();
}
};
timer.start();
return root;
}
private void onUpdate() {
}
#Override
public void start(Stage primaryStage) throws Exception {
root = new Pane();
primaryStage.setTitle("Space Invaders");
primaryStage.setScene(new Scene(createContent()));
primaryStage.getScene().setOnKeyPressed(event -> {
switch (event.getCode()) {
case SPACE:
MediaPlayer shotSound = new MediaPlayer(shot);
shotSound.setVolume(0.1);
shotSound.play();
count++;
System.out.println(count);
}
});
primaryStage.show();
}
public static void main (String[] args){
launch(args);
}
}
For what you are trying to do the AudioClip class is probably much better suited as the full blown MediaPlayer. It also probably not a good idea to initialize a new player every time you get an event.

How to return value from a stage before closing it?

I have a "main stage" where I press a button to open a "second stage" where I have a table, the user selects one item of the the table and click on "asignar" button (which is just a confirm button), once clicked, it must return the code of the item selected in the table to the main stage and close the second stage.
Here is the code that matters.
I have an INT variable which must take the value of a function:
codigo = controller.setVista(this, usuario, password);
The "setVista" function goes like this:
public int setVista(ListHorarios vista, String usuario, String password) {
this.vista = vista;
this.usuario = usuario;
this.password = password;
this.inicializarTabla();
this.actualizarTabla(0, "%");
btnSeleccionar.setOnAction(e -> {
asignarSeleccion();
Stage stage = (Stage) btnSeleccionar.getScene().getWindow();
stage.close();
});
return codigo_horario;
}
And the "asignarSeleccion" like this:
private void asignarSeleccion() {
final HorarioTableModelo aux_horario = getTablaSeleccionada();
posicion = datos.indexOf(aux_horario);
if (aux_horario != null) {
codigo_horario = aux_horario.getCodigo();
}
}
My problem is that I can't get the "codigo_horario" value into the first variable "codigo" before the stage closes, what do I am missing?
Here is a possible example. The structure is the same as in the answer in my comment.
The second Stage is opened through a "controller" that is stores the data that should be returned even when the Stage is closed and exposes a getter to be used to retrieve the value from the outer world.
import javafx.application.Application;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
Button bSecondStage = new Button("Show second Stage");
bSecondStage.setOnAction(e -> {
WindowController wc = new WindowController();
wc.showStage();
System.out.println(wc.getData());
});
root.setCenter(bSecondStage);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
class WindowController {
private String data;
void showStage() {
Stage stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
VBox root = new VBox();
Scene scene = new Scene(root);
TextField tf = new TextField();
Button submit = new Button("Submit");
submit.setOnAction(e -> {
data = tf.getText();
stage.close();
});
root.getChildren().addAll(tf, submit);
stage.setScene(scene);
stage.showAndWait();
}
String getData() {
return data;
}
}
}
You can write your own Stage class with a return statement.
public class MyStage extends Stage {
public String showAndReturn(myFXControll controll) {
super.showAndWait();
return controll.getReturn();
}
}
After that you have to define a return function to your controller.
public class TableFilterControll implements Initializable {
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
}
public String getReturn() {
return "I'm a nice return value"; //return what you want controlled by your controller class
}
}
Now you can controll your return from the parent controller.
String retValue=myStage.showAndReturn(childControll);
System.out.println(retValue);
I think this is a good solution for clean code. And you can style your FXML with Screne Builder.
There is some error in 4baad4's example. If the method in the controller is iCanGetDataBeforeClose, then that's what should be called:
String someValue = controller.iCanGetDataBeforeClose();
But even that didn't work right for me. I actually got this to work without using setOnCloseRequest at all. In the form controller, I had a method like this:
public boolean getCompleted() {
return this.finished;
}
Then in the form calling it:
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("myView.fxml"));
AnchorPane pane = (AnchorPane) loader.load();
myViewController controller = loader.getController();
Scene scene = new Scene(pane);
Stage stage = new Stage();
stage.setScene(scene);
stage.showAndWait();
if (controller.getCompleted()){
doStuff();
}
One might think that since the stage had exited that the controller would throw a null reference exception, but it didn't, and it returned the correct response.
This solution works and is simplest proposed IMHO.
In my main controller I create Stage. Load controller which I can use like any class. And by creating an event I can get data just before closing the window.
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/package/mySceneBuilderWindow.fxml"));
final Pane rootPane = (Pane)loader.load();
Scene scene = new Scene(rootPane);
Stage stage = new Stage();
stage.setTitle("Optional title");
stage.setScene(scene);
mySceneBuilderWindowController controller = loader.<mySceneBuilderWindowController>getController();
controller.iCanSendDataToCtrl("String for now"); // sending data or init textFields...
stage.show();
/* set event which is fired on close
// How to close in the other window... (pressing X is OK too)
#FXML private Button fxidSave = new Button(); // global var
#FXML private void handleSaveButton() {
Stage stage = (Stage) fxidSave.getScene().getWindow();
stage.close(); // closes window
}
*/
stage.setOnCloseRequest((EventHandler<WindowEvent>) new EventHandler<WindowEvent>() {
public void handle(WindowEvent we) {
String iCanGetDataBeforeClose = controller.getData();
System.out.println(iCanGetDataBeforeClose);
// static class can be used aswell -> System.out.println(Context.getMyString());
}
});
} catch (IOException e) {
System.out.println("something wrong with .fxml - name wrong? path wrong? building error?");
}
My other mySceneBuilderWindowController methods:
public void iCanSendDataToCtrl(String giveMe) {
// do something ex. myTextBox.setText(giveMe);
}
public String iCanGetDataBeforeClose() {
// do something ex. return myTextBox.getText();
}

JavaFX MultiThreading Flashing Lights

I have two circles redCircle and greenCircle : -
Circle greenCircle = new Circle(250,150 ,100, Color.TRANSPARENT);
Circle redCircle = new Circle(250,450,100,Color.TRANSPARENT);
greenCircle.setStroke(Color.GREEN);
greenCircle.setStrokeWidth(4);
group.getChildren().add(greenCircle);
redCircle.setStroke(Color.RED);
redCircle.setStrokeWidth(4);
group.getChildren().add(redCircle);
Basically I want Circle to turn on and off twice in 2 seconds. So I am able to turn on Light then wait for 0.5 second , turn off and again wait for 0.5 second and turn on Light. I am not about to turn off after 0.5 second.
public class LightOn {
public Task<Void> runLightOn() throws InterruptedException {
return new Task<Void>(){
#Override
protected Void call() throws Exception {
greenCircle.setFill(Color.GREEN);
return null;
}
};
}
}
public class LightOff {
public void perform() throws InterruptedException {
LightOn onL = new LightOn();
Task<Void> runLinghtOnTask = onL.runLightOn();
runLinghtOnTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
Logger.getLogger(MovementEventsDemo.class.getName()).log(Level.SEVERE, null, ex);
}
greenCircle.setFill(Color.TRANSPARENT);
nextFunction();
}
});
new Thread(runLinghtOnTask).start();
}
}
public void nextFunction(){
Task<Void> sleeper2 = new Task<Void>() {
#Override
protected Void call() throws Exception {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
return null;
}
};
sleeper2.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
if(greenCircle.getFill()==Color.GREEN) {
greenCircle.setFill(Color.TRANSPARENT);
}else {
greenCircle.setFill(Color.GREEN);
}
}
});
new Thread(sleeper2).start();
}
I use this to execute : -
LightOff lf = new LightOff();
lf.perform();
The reason your code is not working is that an exception is being thrown in the call() method of the Task returned by runLightOn(), because you are changing the UI from a background thread. If you catch the exception or register an onFailed handler with the Tasks you will be able to log the exception.
For functionality like this, where you have specific timepoints (KeyFrames) at which you want values to change, use a Timeline instead of messing with multithreading:
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class FlashingLight extends Application {
#Override
public void start(Stage primaryStage) {
Circle circle = new Circle(250, 150, 100, Color.TRANSPARENT);
circle.setStroke(Color.GREEN);
circle.setStrokeWidth(4);
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0.5), e -> circle.setFill(Color.GREEN)),
new KeyFrame(Duration.seconds(1.0), e -> circle.setFill(Color.TRANSPARENT))
);
timeline.setCycleCount(2);
Pane pane = new Pane(circle);
Scene scene = new Scene(pane, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
timeline.play();
}
public static void main(String[] args) {
launch(args);
}
}
As a slight alternative, you can replace
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0.5), e -> circle.setFill(Color.GREEN)),
new KeyFrame(Duration.seconds(1.0), e -> circle.setFill(Color.TRANSPARENT))
);
with
BooleanProperty on = new SimpleBooleanProperty();
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0.5), new KeyValue(on, true)),
new KeyFrame(Duration.seconds(1.0), new KeyValue(on, false))
);
circle.fillProperty().bind(
Bindings.when(on)
.then(Color.GREEN)
.otherwise(Color.TRANSPARENT));
The added benefits here are that you create a boolean value representing whether or not the light is on, which may be useful elsewhere in your logic, and that you separate the logic (on/off, and the timing) from the display (color, etc).

How to update text box in JavaFX Application after 5 seconds of running?

I have a single controller class "FXController.java" and my FXApplication.java that extends "Application" and contains the launch code. In a separate class "TestFX.java" I call the start method in the "FXApplication.java" that starts the gui. I want to be able to access its controller so that I can change the text within a textfield of the controller. In my FXApplication.java, within the "launch" method I create a variable for the FXLoader and use the "getController" method and set it to a public variable: public FXController theController.
Within the TestFX.java, after I call the "start" method in the main method that launches FXApplication.java in a new runnable, I try to access the controller to change the contents of a single textfield, I get an exception that says controller is null. What is the proper way for me to change the contents of the textfield? I feel that the threading is causing problems.
What I am trying to do in my main method is:
Launch the JavaFX Application/GUI
5 seconds later (sleep), change the text of the text field in FXController.java to "Hello World".
Note that the fxml file loaded/used by FXApplication.java is pointed correctly to the FXController.java. Am wondering if there is some way to access the controller despite having spawned a new runnable for the FX application.
FXApplication.java
public class FXApplication extends Application {
public FXController theController;
public void start() {
Application.launch(FXApplication.class, args);
}
#Override
public void start(Stage stage) throws Exception {
FXMLLoader fxmll = new FXMLLoader(getClass().getResource("fxml_example.fxml"));
Parent root = fxmll.load();
theController = fxmll.getController();
stage.setTitle("FXML Welcome");
stage.setScene(new Scene(root, 300, 275));
stage.show();
}
}
My TestFX.java
public class TestFX {
public static FXApplication fxApp = new FXApplication();
public ExecutorService execs;
public Future<?> fut;
TestFX(ExecutorService execs) {
this.execs = execs;
}
public void start() {
fut = execs.submit(new Runnable() {
#Override
public void run() {
fxApp.start();
}
});
}
public static void main(String[] args) {
ExecutorService execs = Executors.newCachedThreadPool();
TestFX testFx = new TestFX(execs);
testFx.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//fxApp.theController.setTextBoxText("Hello Word");
Platform.runLater(() -> fxApp.theController.setTextBoxText("Hello Word"));
}
Stuff that you want to do like this, you should use the Task class. This does all the heavy lifting for you and all you have to do is set up the code you want to run the FXAT when the task completes. Here's an example, I've left out the controller stuff, because it just clutters up the concepts:
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class HelloWorld extends Application {
private static Label label;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World!");
label = new Label();
label.setText("Waiting...");
StackPane root = new StackPane();
root.getChildren().add(label);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
Task<Void> sleeper = new Task<Void>() {
#Override
protected Void call() throws Exception {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
return null;
}
};
sleeper.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
label.setText("Hello World");
}
});
new Thread(sleeper).start();
}
}
The "sleeper" Task doesn't do anything except sleep, but it's going to sleep on a new thread so the FXAT can keep on responding to screen activity. Then when the sleep finishes, the event handler for the succeed will run on the thread that instantiated the Task, in this case the FXAT.
Your code has two problems.
First, you are calling the static FXMLLoader.load(URL) method, instead of calling load on your FXMLLoader instance. Consequently, the FXMLLoader instance never gets to initialize its controller. You need
FXMLLoader fxmll = new FXMLLoader(getClass().getResource("fxml_example.fxml"));
Parent root = fxmll.load();
The second issue is that you are then changing the text of the text box from a background thread, instead of from the FX Application Thread. (Unless you're handling this in the controller class: you don't show the code for that.) You need
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Platform.runLater(() -> fxApp.theController.setTextBoxText("Hello Word"));
You can also do this with a PauseTransition:
PauseTransition pause = new PauseTransition(Duration.seconds(5));
pause.setOnFinished(event -> fxApp.theController.setTextBoxText("Hello Word"));
pause.play();

Redraw javafx scene [duplicate]

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

Resources