1

Trying to add a label in my program where the number on it reduces by one per second, but the label only appears once all the sleeps are completed

@FXML
private Label successAccCrn;
successAccCrn.setVisible(false);
successAccCrn.setVisible(true);
successAccCrn.setText("Account created, page closes in 3");
java.util.concurrent.TimeUnit.SECONDS.sleep(1);
successAccCrn.setText("Account created, page closes in 2");
java.util.concurrent.TimeUnit.SECONDS.sleep(1);
successAccCrn.setText("Account created, page closes in 1");
java.util.concurrent.TimeUnit.SECONDS.sleep(1);

Attempted to use Thread.sleep(millis) as well but once all the sleeps are finished then the line successAccCrn.setVisible(true) shows with text "Account created, page closes in 1", meaning that the two other setTexts could not be seen

sadgeworth
  • 11
  • 1
  • Maybe [this](https://stackoverflow.com/questions/53242990/how-to-make-a-barchart-behave-like-a-timer/53247585#53247585) or [this](https://stackoverflow.com/questions/9966136/javafx-periodic-background-task) can help. – SedJ601 Jul 16 '23 at 00:54
  • 1
    Timeline solved the issue, thank you very much! – sadgeworth Jul 16 '23 at 03:07

1 Answers1

4

There are a couple things wrong with your current approach.

  1. You must never sleep, block, or perform long-running tasks on the JavaFX Application Thread. Otherwise, you will cause the application to become unresponsive because the thread cannot process user-generated events or schedule render "pulses".

  2. The scene will not be redrawn until the JavaFX Application Thread has finished its current work. That means setting a label's text property to different values via consecutive statements will not work as you expect. Only the last update to the text property will be rendered.

You basically want a periodic task executed on the JavaFX Application Thread. The way to implement that is via an animation. See my answer here for more information.

In your case, your task is essentially a countdown timer. A relatively straightforward way to implement a countdown timer in JavaFX is to use a PauseTranstion. Set the transition's duration to the desired value, then make use of the currentTime property to calculate the remaining time of the animation. You can also use the onFinished property to perform an action once the transition completes.

Here's an example:

import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.util.Duration;


public class Main extends Application {

    @Override
    public void start(Stage primaryStage) {
        Button button = new Button("Create account...");
        button.setOnAction(e -> {
            e.consume();
            showAccountCreatedAlert(primaryStage);
        });

        primaryStage.setScene(new Scene(new StackPane(button), 600, 400));
        primaryStage.show();
    }

    private void showAccountCreatedAlert(Window owner) {
        Alert alert = new Alert(AlertType.INFORMATION);
        alert.initOwner(owner);
        alert.setHeaderText("Account Created");
        
        // the countdown timer
        PauseTransition timer = new PauseTransition(Duration.seconds(5));
        alert.contentTextProperty()
                .bind(createRemainingSecondsMessage("Dialog closing in %d seconds.", timer));
        timer.setOnFinished(e -> alert.close()); // close the alert when the timer completes

        // Start the timer when the alert is shown. Stop it if the alert is
        // closed before the timer completes.
        alert.setOnShown(e -> timer.play());
        alert.setOnHidden(e -> timer.stop());
        alert.showAndWait();
    }

    private StringBinding createRemainingSecondsMessage(String format, PauseTransition timer) {
        return Bindings.createStringBinding(() -> {
            Duration timeRemaining = timer.getDuration().subtract(timer.getCurrentTime());
            int secondsRemaining = (int) timeRemaining.toSeconds() + 1;
            return String.format(format, secondsRemaining);
        }, timer.currentTimeProperty(), timer.durationProperty());
    }

    public static void main(String[] args) {
        Application.launch(Main.class, args);
    }
}
Slaw
  • 37,820
  • 8
  • 53
  • 80