javajavafxjava-threadspause

Function for making the code wait in javafx


Im new to using javaFx and programming in general so this might be a stupid question. Im trying to make a function that makes the thread wait without crashing the program which would result in something like the image above. What Im trying to achive

I have tried using thread.sleep but it crashed the gui and also something like a Timeline or PauseTransition like this:

public static void wait(int milliseconds) {
        Timeline timeline = new Timeline(new KeyFrame(Duration.millis(milliseconds)));
        timeline.setOnFinished(event -> {

        });
        timeline.play();
    }

but it doesn't work since the javafx things work on a different thread.

edit: Something to keep in mind is that there isn't something specific to do after the pause so since the function doesn't know why I need the pause for it just needs to stop the main thread for x amount of time without crashing the gui.

Example of what I mean:

System.out.println("some information");
    pause(4000);
    System.out.println("idk");
    pause(1000);
    button.setVisible(true);
    pause(5000);
    MyImage.setImage(AImage);

Solution

  • Given your example, the best solution is to just use a Timeline (or some other animation API).

    var timeline =
        new Timeline(
            new KeyFrame(Duration.seconds(4), _ -> System.out.println("idk")),
            new KeyFrame(Duration.seconds(5), _ -> button.setVisible(true)),
            new KeyFrame(Duration.seconds(10), _ -> MyImage.setImage(AImage)));
    timeline.playFromStart();
    

    Note: Using _ as the name for unused variables is a preview feature in Java 21; it is a standard feature in Java 22+.

    The reason the times are different than in your question is because the duration of a key frame is absolute (i.e., they do not stack on top of previous key frames). You can abstract the creation of the timeline to a method, even making it so the times do "stack".

    import javafx.animation.KeyFrame;
    import javafx.animation.Timeline;
    import javafx.util.Duration;
    
    public record DelayedAction(Duration delay, Runnable action) {}
    
    // enclosing class omitted for brevity
    public static Timeline createActionSequence(DelayedAction... sequence) {
      if (sequence.length == 0) throw new IllegalArgumentException("empty sequence");
    
      var timeline = new Timeline();
      var duration = Duration.ZERO;
      for (var delayedAction : sequence) {
        duration = duration.add(delayedAction.delay());
        var action = delayedAction.action();
        var frame = new KeyFrame(duration, _ -> action.run());
        timeline.getKeyFrames().add(frame);
      }
      return timeline;
    }
    

    That said, if you really want to emulate Thread::sleep while still allowing the FX thread to do work, then you can enter a nested event loop and then exit it after a given amount of time. For example:

    import javafx.animation.PauseTransition;
    import javafx.application.Platform;
    import javafx.util.Duration;
    
    // enclosing class omitted for brevity
    public static void sleepFxThread(Duration duration) {
      if (duration.isUnknown() || duration.isIndefinite())
        throw new IllegalArgumentException("duration cannot be indefinite or unknown");
      if (!Platform.canStartNestedEventLoop())
        throw new IllegalStateException(
            "sleepFxThread is not allowed during animation or layout processing");
    
      if (duration.greaterThan(Duration.ZERO)) {
        var delay = new PauseTransition(duration);
        delay.setOnFinished(_ -> Platform.exitNestedEventLoop(delay, null));
        delay.playFromStart();
    
        Platform.enterNestedEventLoop(delay);
      }
    }
    

    Note: Platform::canStartNestedEventLoop was added in JavaFX 21.

    Be aware that the above can lead to unintuitive behavior when you are already in a nested event loop, such as after calling showAndWait() on a dialog.

    Alert alert = new Alert(Alert.AlertType.INFORMATION);
    alert.setContentText("Close this alert immediately.");
    Platform.runLater(() -> sleepFxThread(Duration.seconds(10)));
    alert.showAndWait();
    

    The above alert.showAndWait() call cannot return until the sleep completes, because the sleep started a nested event loop inside an already-existing nested event loop. In other words, if the user closes the alert within 10 seconds, the program will not continue until those 10 seconds pass.