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);
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.