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() {
}
});
Related
i have a problem with the Timeline in JavaFX : the Timeline is locked at 1fps.
KeyFrames aren't triggerred more than this, even if i've put three keyframes :
60 times per second
120 times per second
1 time per second
They're all triggered at the same time : 1 second
TickSystem class :
package TickSystem;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
public class TickSystem implements EventHandler<ActionEvent> {
private KeyFrame kfU; // update
private KeyFrame kfD; // draw
private KeyFrame kfFPS; // FPS count
public Rectangle r;
public int curFrame = 0;
public int tick = 0;
public final Timeline gameLoop = new Timeline(120);
public final Duration updateTime = Duration.millis((double)1000/60); // 60 times per seconds
public final Duration drawTime = Duration.millis((double)1000/120); // 120 times per seconds
public int fps;
private int lastFrames = 0;
public TickSystem(Rectangle r){
this.r = r;
this.kfU = new KeyFrame(updateTime,"tickKeyUpdate", this::handle);
this.kfD = new KeyFrame(drawTime,"tickKeyDraw", this::handleDraw);
this.kfFPS = new KeyFrame(Duration.seconds(1),"tickKeyFPS", this::handleFPS);
this.gameLoop.setCycleCount(Timeline.INDEFINITE);
this.gameLoop.getKeyFrames().add(this.kfU);
this.gameLoop.getKeyFrames().add(this.kfD);
this.gameLoop.getKeyFrames().add(this.kfFPS);
}
public void start(){
this.gameLoop.play();
}
public void pause(){
this.gameLoop.pause();
}
public void stop(){
this.gameLoop.stop();
}
#Override
public void handle(ActionEvent ae) { // for update
this.tick++;
}
public void handleDraw(ActionEvent ae){ // for draw
this.curFrame++;
this.r.setWidth(curFrame);
}
public void handleFPS(ActionEvent ae) { // for FPS
this.fps = this.curFrame - this.lastFrames;
this.lastFrames = this.curFrame;
System.out.println(this.fps);
}
}
Main class :
package TickSystem;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Data");
primaryStage.setResizable(true);
Group root = new Group();
Scene scene = new Scene(root,400,400);
Rectangle r = new Rectangle(10,10,100,100);
r.setFill(Color.RED);
root.getChildren().add(r);
TickSystem loop = new TickSystem(r);
primaryStage.setScene(scene);
primaryStage.show();
loop.start();
}
}
So, the rectangle must gain 1px on width each time the handleDraw function is called, so 120 times per second.
Actually, he only gain one pixel per second. And i'm at 1fps on the handleFPS function.
This function must print the number of times the handleDraw function has been called each seconds
EDIT :
I've made these three KeyFrames cause i try to make a 2D game.
I've already make a good part of this game on Java with Swing and i need to update infos (like player poisition) 60 times per second but i try to draw informations on screen 120 times per second.
JavaFX sounds better to me for the GUI, so i left java swing behind.
These class are for testing and i'm new on JavaFX. Thanks for your time.
You have three key frames; one at 1/120 second, one at 1/60 second, and one at 1 second. Since the longest duration of any key frame is one second, the duration of one cycle of the timeline is one second.
Therefore, during one cycle of the timeline, the following three things happen:
At 1/120 second, handleDraw() is invoked
At 1/60 second, handle() is invoked
At 1 second, handleFPS() is invoked
So during one cycle of the timeline (1 second), handleDraw() and handle() are invoked once each.
You set the cycle count to INDEFINITE, so once one cycle is completed, it repeats; this happens indefinitely.
One solution is to use a separate timeline for each of the individual tasks. This will not add any appreciable overhead to the application.
(As an aside: there is no point here in implementing EventHandler. You never use an instance of TickSystem as an event handler; you only use the lambda expressions.)
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
public class TickSystem {
private KeyFrame kfU; // update
private KeyFrame kfD; // draw
private KeyFrame kfFPS; // FPS count
public Rectangle r;
public int curFrame = 0;
public int tick = 0;
public final Timeline gameLoop = new Timeline(60);
private final Timeline drawLoop = new Timeline(120);
private final Timeline fpsLoop = new Timeline(1000);
public final Duration updateTime = Duration.millis((double)1000/60); // 60 times per seconds
public final Duration drawTime = Duration.millis((double)1000/120); // 120 times per seconds
public int fps;
private int lastFrames = 0;
public TickSystem(Rectangle r){
this.r = r;
this.kfU = new KeyFrame(updateTime,"tickKeyUpdate", this::handleUpdate);
this.kfD = new KeyFrame(drawTime,"tickKeyDraw", this::handleDraw);
this.kfFPS = new KeyFrame(Duration.seconds(1),"tickKeyFPS", this::handleFPS);
this.gameLoop.setCycleCount(Timeline.INDEFINITE);
this.drawLoop.setCycleCount(Timeline.INDEFINITE);
this.fpsLoop.setCycleCount(Timeline.INDEFINITE);
this.fpsLoop.getKeyFrames().add(this.kfFPS);
this.gameLoop.getKeyFrames().add(this.kfU);
this.drawLoop.getKeyFrames().add(this.kfD);
}
public void start(){
this.gameLoop.play();
this.fpsLoop.play();
this.drawLoop.play();
}
public void pause(){
this.gameLoop.pause();
this.fpsLoop.pause();
this.drawLoop.pause();
}
public void stop(){
this.gameLoop.stop();
this.drawLoop.stop();
this.fpsLoop.stop();
}
public void handleUpdate(ActionEvent ae) { // for update
this.tick++;
}
public void handleDraw(ActionEvent ae){ // for draw
this.curFrame++;
this.r.setWidth(curFrame);
}
public void handleFPS(ActionEvent ae) { // for FPS
this.fps = this.curFrame - this.lastFrames;
this.lastFrames = this.curFrame;
System.out.println(this.fps);
}
}
Or, more succinctly:
public class TickSystem {
private Rectangle r;
private int curFrame = 0;
private int tick = 0;
private final List<Timeline> timelines = new ArrayList<>();
private int fps;
private int lastFrames = 0;
public TickSystem(Rectangle r){
this.r = r;
timelines.add(createTimeline(60, this::handleUpdate));
timelines.add(createTimeline(120, this::handleDraw));
timelines.add(createTimeline(1, this::handleFPS));
}
private Timeline createTimeline(int frequency, EventHandler<ActionEvent> handler) {
Timeline timeline = new Timeline(frequency);
timeline.getKeyFrames().add(new KeyFrame(Duration.millis(1000.0 / frequency), handler));
timeline.setCycleCount(Animation.INDEFINITE);
return timeline;
}
public void start(){
timelines.forEach(Timeline::play);
}
public void pause(){
timelines.forEach(Timeline::pause);
}
public void stop(){
timelines.forEach(Timeline::stop);
}
public void handleUpdate(ActionEvent ae) { // for update
this.tick++;
}
public void handleDraw(ActionEvent ae){ // for draw
this.curFrame++;
this.r.setWidth(curFrame);
}
public void handleFPS(ActionEvent ae) { // for FPS
this.fps = this.curFrame - this.lastFrames;
this.lastFrames = this.curFrame;
System.out.println(this.fps);
}
}
Another solution would be to use an AnimationTimer: see https://stackoverflow.com/a/60685975/2189127
You should also note that your FPS keyframe/timeline is not really measuring frames per second, in the sense of how frequently the scene graph is repainted. It is only measuring how frequently the width of the rectangle is updated (how often the property value is changed).
So im trying to stop a single thread when I have multiple threads running, here is the code im using to initialise the threads. Basically I have multiple textFields in javafx, and when a button is clicked on the screen, it fills the textFields, one by one, with an incrementing timer. Now I also have a button for each of the textfields to clear it, but the problem is when I clear it, because the thread is still running, the timer vanishes for a second and comes back because of the line 'orderTimes.get(boxNo).setText(minute + second);' in the code.
Now what I've tried is creating a list of threads and I've tried implementing it below but it doesn't work, this is so I can call each individual thread if its button to clear has been clicked.
Does anyone know how I can close/stop only one single thread out of multiple that are running? If more info is needed just let me know, thanks.
public static void createIncrementingTimer(int boxNo, List<TextField> orderTimes) {
minutesList.set(boxNo, 0);
secondsList.set(boxNo, 0);
state = true;
new Thread(threadList.get(boxNo)) {
int currentMinutes = 0;
int currentSeconds = 0;
public void run() {
for (;;) {
if (state = true) {
try {
sleep(1000);
if (secondsList.get(boxNo) > 59) {
secondsList.set(boxNo, 0);
currentSeconds = 0;
minutesList.set(boxNo, currentMinutes + 1);
currentMinutes++;
}
if (secondsList.get(boxNo) < 10) {
second = ":0" + Integer.toString(secondsList.get(boxNo));
} else {
second = ":" + Integer.toString(secondsList.get(boxNo));
}
secondsList.set(boxNo, currentSeconds + 1);
currentSeconds++;
if (minutesList.get(boxNo) < 10) {
minute = "0" + Integer.toString(minutesList.get(boxNo));
} else {
minute = Integer.toString(minutesList.get(boxNo));
}
orderTimes.get(boxNo).setText(minute + second);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
};
threadList.get(boxNo).start();
}
The code I'm using to clear the textfields is below, with orderTimes being the list of textFields that I'm trying to clear.
public static void eraseBox(int clickedButtonNumber, List<TextArea> orderContentsList, List<TextField> tableNumbers, List<TextField> orderNumbers, List<TextField> orderTimes) {
orderContentsList.get(clickedButtonNumber).setText(null);
tableNumbers.get(clickedButtonNumber).clear();
orderNumbers.get(clickedButtonNumber).clear();
orderTimes.get(clickedButtonNumber).clear();
}
I would suggest you try to avoid Threads. The Animation API is designed to make doing work that would normally be done in a Thread easier. In this example, the IncrementingTimer class consists of two Labels and three Buttons. The Labels are used to show the time. The Buttons are used to control the Timeline. The Timeline is used to increment the Labels value each second or every sixty seconds. I have added three IncrementingTimers to the app.
Main
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* JavaFX App
*/
public class App extends Application {
#Override
public void start(Stage stage) {
var scene = new Scene(new VBox(new IncrementingTimer(), new IncrementingTimer(), new IncrementingTimer()), 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
IncrementingTimer
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.util.Duration;
/**
*
* #author blj0011
*/
final public class IncrementingTimer extends HBox
{
IntegerProperty secondsCounter = new SimpleIntegerProperty();//Keeps up with seconds
IntegerProperty minutesCounter = new SimpleIntegerProperty();//Keeps up with minutes
Label lblSeconds = new Label();//Displays the seconds
Label lblMinutes = new Label();//Displays the minutes
Label lblColon = new Label(":");//Display the colon between minutes and seconds
Button btnPlay = new Button("Play");//Plays the Timeline
Button btnStop = new Button("Stop");//Stops the Timeline
Button btnPause = new Button("Pause");//Pauses the Timeline
Timeline timeline;//Used to run code that changes the Labels. This Timeline runs every one second.
public IncrementingTimer()
{
lblSeconds.textProperty().bind(secondsCounter.asString("%02d"));//Binds the seconds label to the seconds counter. Sets the String to always show two digits. Exmaple 1 is shown as 01.
lblMinutes.textProperty().bind(minutesCounter.asString("%02d"));//Binds the minutes label to the minutes counter. Sets the String to always show two digits. Exmaple 1 is shown as 01.
getChildren().addAll(lblMinutes, lblColon, lblSeconds, btnPlay, btnStop, btnPause);
timeline = new Timeline(new KeyFrame(Duration.seconds(1), (event) -> {//Replace the one with .016 to speed this up for testing purposes.
secondsCounter.set(secondsCounter.get() + 1);
if (secondsCounter.get() == 60) {
secondsCounter.set(0);
minutesCounter.set(minutesCounter.get() + 1);
if (minutesCounter.get() == 60) {
minutesCounter.set(0);
}
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
btnPlay.setOnAction((event) -> {
timeline.play();
});
btnPause.setOnAction((event) -> {
timeline.pause();
});
btnStop.setOnAction((event) -> {
timeline.stop();
secondsCounter.set(0);
minutesCounter.set(0);
});
this.setAlignment(Pos.CENTER);
}
}
As recommended and demonstrated by Sedric, use JavaFx Animation tools for the counters.
The following one-file mre demonstrating implementation of counters using two different animation tools.
One uses PauseTransition and uses Timeline, each with its stop button.
(copy-paste the entire code into Timers.java and run)
import java.io.IOException;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.PauseTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Timers extends Application {
#Override public void start(final Stage stage) throws IOException {
VBox root = new VBox(new CounterPane(new TimeLineCounter()), new CounterPane(new PauseTransitionCounter()));
stage.setScene(new Scene(root));
stage.show();
}
public static void main(final String[] args) { launch(args); }
}
class CounterPane extends HBox{
private final Counter counter;
CounterPane(Counter counter) {
super(5);
this.counter = counter; //todo: check not null
Button stopBtn = new Button("Stop");
stopBtn.setOnAction(e->stop());
getChildren().addAll(stopBtn, counter);
}
void stop(){
counter.getAnimation().stop();
}
}
abstract class Counter extends Label {
protected int count = 0;
public Counter() {
setAlignment(Pos.CENTER); setPrefSize(25, 25);
count();
}
abstract void count();
abstract Animation getAnimation();
}
class TimeLineCounter extends Counter {
private Timeline timeline;
#Override
void count() {
timeline = new Timeline();
timeline.setCycleCount(Animation.INDEFINITE);
final KeyFrame keyFrame = new KeyFrame(
Duration.seconds(1),
event -> { setText(String.valueOf(count++) ); }
);
timeline.getKeyFrames().add(keyFrame);
timeline.play();
}
#Override
Animation getAnimation() {
return timeline;
}
}
class PauseTransitionCounter extends Counter {
private PauseTransition pauseTransition;
#Override
void count() {
pauseTransition = new PauseTransition(Duration.seconds(1));
pauseTransition.setOnFinished(event ->{
setText(String.valueOf(count++) );
pauseTransition.play();
});
pauseTransition.play();
}
#Override
Animation getAnimation() {
return pauseTransition;
}
}
The if(state=true) should rather be if(state==true) or just if(state), but in fact the for(;;) could do the entire thing as while(state), simply shutting down the thread when you set state=false.
Then, fully stopping the thread could happen as state=false;threadList.get(boxNo).join();, and you can clear the field only after that (since the thread will set it to something in the last step too).
With a simpler approach you could throw away the state, and revert to for(;;), with the twist of having the try-catch() around the loop, outside. This way you can use threadList.get(boxNo).interrupt();threadList.get(boxNo);.join(); to stop the thread, and on top of that it will be immediate, as the sleep() ends immediately when the thread is interrupted.
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();
}
I'm using JavaFx that comes with JDK 8.0, on a MacBook Pro with 2,4 GHz Intel Core 2 Duo processor and 4GB of RAM.
I have a strange behavior, using the following class:
import com.sun.javafx.perf.PerformanceTracker;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
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.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class HelloWorld extends Application {
private static PerformanceTracker tracker;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World!");
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene= new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
Label label1 = new Label();
Label label2 = new Label();
((Pane)root).getChildren().addAll(label1, label2);
scene.setOnKeyPressed((e)->{
label2.setText(label1.getText());
});
tracker = PerformanceTracker.getSceneTracker(scene);
AnimationTimer frameRateMeter = new AnimationTimer() {
#Override
public void handle(long now) {
label1.setText(String.format("Current frame rate: %.3f fps", getFPS()));
}
};
//frameRateMeter.start();
}
private float getFPS () {
float fps = tracker.getAverageFPS();
tracker.resetAverageFPS();
return fps;
}
}
This code when executed uses CPU between a percentage of 0.2% to max 10%.
If I remove the comment from :
frameRateMeter.start();
I get that the same code uses CPU from 20% to 40%.
This is just an example but, the application I wrote, commenting out the line above, runs using around 40% of CPU and removing the comment runs near 100% of CPU.
Is that normal? I notice that also adding any time of Timeline (also very simple one) that execute continuously, produces a ridiculous use of CPU.
Is really so expensive to use animation in JavaFX or there is something I have missed?
Any help would be really appreciate.
I think you are updating FPS value in Label very frequently:
The class AnimationTimer allows to create a timer, that is called in each frame while it is active.
When you update text in the label1 the JavaFX draw new frames again and again. You can easy check this: write FPS to STDOUT instead of label1:
...
System.out.println(String.format("Current frame rate: %.3f fps", tracker.getAverageFPS()));
// label1.setText(String.format("Current frame rate: %.3f fps", tracker.getAverageFPS()));
...
In this case you should see less FPS rate.
So, try to update FPS value every one or half second use any Java/JavaFX timer i.e.:
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(500), event -> {
label1.setText(String.format("Current frame rate: %.3f fps", tracker.getAverageFPS()));
}));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
Average CPU value is about 1.8-2% on my macOS.
Here is an easy way to count FPS[frames per second] from AnimationTimer without using any other class:
/**
* AnimationTimer .
*
* #author GOXR3PLUS
*/
public class PaintService extends AnimationTimer {
/** The next second. */
long nextSecond = 0L;
/** The Constant ONE_SECOND_NANOS. */
private static final long ONE_SECOND_NANOS = 1_000_000_000L;
/**
* When this property is <b>true</b> the AnimationTimer is running
*/
private volatile SimpleBooleanProperty running = new SimpleBooleanProperty(false);
#Override
public void start() {
nextSecond = 0L;
super.start();
running.set(true);
}
#Override
public void stop() {
super.stop();
running.set(false);
}
/**
* #return True if AnimationTimer is running
*/
public boolean isRunning() {
return running.get();
}
/**
* #return Running Property
*/
public SimpleBooleanProperty runningProperty() {
return running;
}
#Override
public void handle(long nanos) {
// -- Show FPS if necessary.
if (true) { //originally i had a variable (showFPS) here
framesPerSecond++;
// Check for 1 second passed
if (nanos >= nextSecond) {
fps = framesPerSecond;
framesPerSecond = 0;
nextSecond = nanos + ONE_SECOND_NANOS;
}
label.setText("FPS: " + fps);
}
}
}
I am trying to print a statement repeatedly using Swing Timer but the statement doesn't gets printed !
What's the mistake I am making ?
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;
public class SwingTimer implements ActionListener {
Timer timer;
public static void main(String[] args) {
SwingTimer obj = new SwingTimer();
obj.create();
}
public void create() {
timer = new Timer(1000, this);
timer.setInitialDelay(0);
timer.start();
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Hello using Timer");
}
}
The javax.swing.Timer probably starts as a daemon thread: it doesn't keep the jvm alive, your main ends, the jvm exits. It post the timer events to the GUI event queue which starts when the first dialog or frame is made visible.
You have to create a JFrame, and make it visible or use the java.util.Timer if you don't need windowing system at all.
The following code shows how to use java.util.Timer:
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo extends TimerTask {
private long time = System.currentTimeMillis();
#Override public void run() {
long elapsed = System.currentTimeMillis() - time;
System.err.println( elapsed );
time = System.currentTimeMillis();
}
public static void main( String[] args ) throws Exception {
Timer t = new Timer( "My 100 ms Timer", true );
t.schedule( new TimerDemo(), 0, 100 );
Thread.sleep( 1000 ); // wait 1 seconde before terminating
}
}
javax.swing.Timer should only be used when using Swing applications. Currently your main Thread is exiting as the Timer uses a daemon Thread. As a workaround you could do:
public static void main(String[] args) {
SwingTimer obj = new SwingTimer();
obj.create();
JOptionPane.showMessageDialog(null, "Timer Running - Click OK to end");
}
An alternative for non-UI applications is to use ScheduledExecutorService