1

I'm really beginner with javaFX and probably I'm missing something, but here my issue: I should show some changes in my gui application sequentially.

My code looks like this (each method performs a change in the gui, but the final result is given by the last method of course):

public void updateGui()
{
    Platform.runLater(() -> {
        doFirstStuff();
        doSecondStuff();
        doThirdStuff();
    });
}

It would be nice if between the execution of each method there was a little delay (1,2 or maybe 3 seconds) for give time to the user to see what happened in the application.

My attempt was put:

Thread.sleep(1000);

after each method call, but since the thread is the JavaFX Application Thread, the result is awful.

Another solution that comes up right now in my mind is:

execute each method in another thread with a Runnable like this:

() -> {
    Platform.runLater(() -> {
        doFirstStuff();
    });
    Thread.sleep(1000);
});

and in updateGui() submit the task in a newSingleThreadExecutor() and wait for the termination of each task. But I don't know, it doesn't seem a good solution.

What is the best way to achieve my goal?

1 Answers1

13

The simplest way is just to use a Timeline:

public void updateGui() {
    final KeyFrame kf1 = new KeyFrame(Duration.seconds(0), e -> doFirstStuff());
    final KeyFrame kf2 = new KeyFrame(Duration.seconds(1), e -> doSecondStuff());
    final KeyFrame kf3 = new KeyFrame(Duration.seconds(2), e -> doThirdStuff());
    final Timeline timeline = new Timeline(kf1, kf2, kf3);
    Platform.runLater(timeline::play);
}

I am assuming here that updateGui() is being called on a background thread (otherwise just remove Platform.runLater(...) and call timeline.play() directly).

If you really want to get your hands dirty with threads, you could achieve the same (more or less) with

public void updateGui() {

    Thread thread = new Thread(() -> {
        try {
            Platform.runLater(() -> doFirstStuff());
            Thread.sleep(1000);
            Platform.runLater(() -> doSecondStuff());
            Thread.sleep(1000);
            Platform.runLater(() -> doThirdStuff());
        } catch (InterrupedException exc) {
            // should not be able to get here...
            throw new Error("Unexpected interruption");
        }
    };
    thread.start();
}

but I think for use cases like this, the Timeline (or other parts of the animation API) are much cleaner (and use fewer resources, since they don't create a new thread).

James_D
  • 201,275
  • 16
  • 291
  • 322
  • How this is working? ' Platform.runLater(timeline::play); } ' first time seen it.. – GOXR3PLUS Aug 30 '16 at 20:28
  • 2
    @GoXr3Plus It's a [method reference](https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html). It's equivalent to `Platform.runLater(() -> timeline.play())`. Since the signature of `timeline.play()` (no params, no return value) is the same as the signature of the abstract method in `Runnable` you can simply pass a method reference in place of the implementation of `Runnable.run()`. – James_D Aug 30 '16 at 20:38
  • Thank you! Just what I needed! I tried both and even my `newSingleThreadExecutor()` solution, all works well for my purpose, but I guess `Timeline` way is the best for this kind of problems. Yes, I'm calling `updateGui()` from a background thread (maybe I should have specify, I'm sorry). Last thing: why the `final` modifier on `KeyFrames` and `Timeline`? –  Aug 31 '16 at 10:26
  • The `final` modifier is not necessary. The timeline and key frames are created on one thread and then accessed from another, so it's important that everything is done in a thread-safe manner. `KeyFrame`s are immutable, so if you force the references created to be final you are assured nothing is changed there, and the only access to the `Timeline` is via the constructor, which is effectively atomic. Modifying these with `final` doesn't guarantee I don't modify properties of the timeline later, but it is a good declaration of intent. – James_D Aug 31 '16 at 11:53