0

I just learnt some news in javafx , then i want to apply them , so I choose to make a counter; But when i setText on my label that doesn't change it; I tested it with System.out.print and that worked well. That's my code :

public class Controller {
@FXML
private Label time;
@FXML
private Button start;
public void Counter(ActionEvent event)
{
    start.setText("hello");
    time.setText("00 : 00 : 00");
    int h=0,m=0,s=0,i=0;
    while(true)
    {
        try {
            s++;
            if(s==60) {s=0;m++;}
            if(m==60) {m=0;h++;}
            System.out.println(Integer.toString(h)+" : "+String.valueOf(m)+" : "+String.valueOf(s));
            time.setText(Integer.toString(h)+" : "+String.valueOf(m)+" : "+String.valueOf(s));

                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }



    }

}
}

thanks for helping ! ;)

  • 2
    First, go read [Concurrency in JavaFX](https://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm) and the consider something [like this](https://stackoverflow.com/questions/9966136/javafx-periodic-background-task) instead – MadProgrammer Mar 31 '18 at 01:49

2 Answers2

1

The important thing here, is you need to understand that any update made to the UI needs be done from within the UI's main thread. You should also avoid blocking the UI's main thread, this will prevent the UI from been updated.

To start with, have a read through Concurrency in JavaFX

When ever you're dealing with time or duration, you should always use a dedicated library, like java.time. Why? Because there isn't actually 24 hours in a day or 60 seconds in a minute - I know, weird isn't it.

You could...

Use Timeline

import java.time.LocalDateTime;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Test extends Application {

    private LocalDateTime startTime;
    private Timeline timer;

    private Label time;

    @Override
    public void start(Stage primaryStage) {
        time = new Label("...");
        Button btn = new Button();
        btn.setText("Start");
        btn.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                if (timer != null) {
                    timer.stop();
                }
                startTime = LocalDateTime.now();
                timer = new Timeline(new KeyFrame(Duration.millis(500), new EventHandler<ActionEvent>() {

                    @Override
                    public void handle(ActionEvent event) {
                        LocalDateTime now = LocalDateTime.now();
                        java.time.Duration duration = java.time.Duration.between(startTime, now);
                        time.setText(format(duration));
                    }
                }));
                timer.setCycleCount(Timeline.INDEFINITE);
                timer.play();
            }
        });

        VBox root = new VBox();
        root.setAlignment(Pos.CENTER);
        root.getChildren().add(time);
        root.getChildren().add(btn);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    protected String format(java.time.Duration duration) {
        long hours = duration.toHours();
        long mins = duration.minusHours(hours).toMinutes();
        long seconds = duration.minusMinutes(mins).toMillis() / 1000;
        return String.format("%02dh %02dm %02ds", hours, mins, seconds);
    }

    public static void main(String[] args) {
        launch(args);
    }

}

Use AnimationTimer

This is possibly the preferred solution, as the timer is called in sync with the frame refresh of the animation engine

import java.time.LocalDateTime;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Test extends Application {

    private LocalDateTime startTime;
    private AnimationTimer timer;

    private Label time;

    @Override
    public void start(Stage primaryStage) {
        time = new Label("...");
        Button btn = new Button();
        btn.setText("Start");
        btn.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                if (timer != null) {
                    timer.stop();
                }
                startTime = LocalDateTime.now();
                timer = new AnimationTimer() {
                    @Override
                    public void handle(long click) {
                        LocalDateTime now = LocalDateTime.now();
                        java.time.Duration duration = java.time.Duration.between(startTime, now);
                        time.setText(format(duration));
                    }
                };
                timer.start();
            }
        });

        VBox root = new VBox();
        root.setAlignment(Pos.CENTER);
        root.getChildren().add(time);
        root.getChildren().add(btn);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    protected String format(java.time.Duration duration) {
        long hours = duration.toHours();
        long mins = duration.minusHours(hours).toMinutes();
        long seconds = duration.minusMinutes(mins).toMillis() / 1000;
        return String.format("%02dh %02dm %02ds", hours, mins, seconds);
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
0

a while(true) loop should never be used to update JavaFX GUI, you can use an AnimationTimer instead

you can try this

public void Counter(ActionEvent event)
{
        start.setText("hello");
        time.setText("00 : 00 : 00");

        AnimationTimer timer = new AnimationTimer() {
            long then=0;
            double sum=0;
            int h=0,m=0,s=0;

            @Override
            public void handle(long now) {
                long dt = now - then;
                sum+=dt;
                if(sum/1000000000>=1) {
                     s++;
                        if(s==60) {s=0;m++;}
                        if(m==60) {m=0;h++;}
                        System.out.println(Integer.toString(h)+" : "+String.valueOf(m)+" : "+String.valueOf(s));
                        time.setText(Integer.toString(h)+" : "+String.valueOf(m)+" : "+String.valueOf(s));
                    sum=0;
                }

                then=now;
            }

        };
        timer.start();
}
SDIDSA
  • 894
  • 10
  • 19