4
\$\begingroup\$

I am doing an assignment and I have a task where I need to make a simulation where under certain conditions it opens a popup window and then closes it after 3 seconds. This is how I did it:

if(condition) {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.submit(() -> {
        Platform.runLater( () -> {
            try {
                Monument atr = ((Monument)matrica[red][kolona]);
                FXMLLoader loader = new FXMLLoader(getClass().getResource("PopUp.fxml"));
                Scene scene = new Scene(loader.load());
                loader.<PopUpController>getController().setImage(atr.getImage());
                Stage stage = new Stage();
                stage.setScene(scene);
                stage.sizeToScene();
                stage.setResizable(false);
                stage.setTitle("Image");
                stage.show();
                sleep(3000);
                ((Stage)(stage.getScene().getWindow())).close();
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            }
        });
    });
    executor.shutdown();
}

I use Platform.runLater because I am accessing the UI from a different thread. My question is, is there any better way of doing this?

\$\endgroup\$
2
\$\begingroup\$

Alternative code:

ScheduledExecutorService executor =  Executors.newSingleThreadScheduledExecutor();
// assuming the stage-setup is perfomed already:
executor.submit(() -> Platform.runLater(stage::show));
executor.schedule(
    () -> Platform.runLater(((Stage)stage.getScene().getWindow()).close())
    , 3
    , TimeUnit.SECONDS);

This approach has multiple advantages:

  • The (costly) stage-setup is moved away from the UI Thread
  • Sleeping on the UI Thread doesn't happen, freeing it to do actual work
  • ScheduledExecutorService is more exact with it's scheduling than a sleep-call. It also doesn't involve having to handle InterruptedException or spurious wakeup

But it also has one glaring disadvantage:

  • You can't directly shutdown the ExecutorService, because that would cancel the scheduled Runnable.

This shouldn't be much of a problem though, since you should not be creating an ExecutorService in method scope in the first place.
Instead you should move multithreading facilities into the class scope (or even into static scope), so as to enable easy cleanup when the application is terminated or the instance reaches the end of it's lifetime.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ You can shutdown the ExecutorService, the scheduled tasks will not be canceled. \$\endgroup\$
    – Nevay
    Sep 5 '17 at 13:31
1
\$\begingroup\$
  1. You create a thread but you don't actually run any code on the thread. All or your code runs inside the Platform.runLater. This means all of your code is running on the JavaFX main thread.

  2. Java developers have already designed for situations where a certain amount of time needs to pass before an action occurs. To do this use PauseTransition.

In my opinion, the best approach for a question like this is to use PauseTransition. You can go here to learn how to handle periodic background task in JavaFX. Below is a sample app this demos this approach.

import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
 *
 * @author blj0011
 */
public class JavaFXApplication162 extends Application
{

    @Override
    public void start(Stage primaryStage)
    {
        Button btn = new Button();
        btn.setText("Say 'Hello World'");
        btn.setOnAction((ActionEvent event) -> {
            //Show popup on button press
            btn.setDisable(true);
            StackPane stackPane = new StackPane(new Label("Popup!"));
            Scene popupScene = new Scene(stackPane, 50, 50);
            Stage popupStage = new Stage();
            popupStage.setScene(popupScene);
            popupStage.show();

            PauseTransition wait = new PauseTransition(Duration.seconds(3));
            wait.setOnFinished((e) -> {
                /*YOUR METHOD*/
                popupStage.close();
                btn.setDisable(false);
            });
            wait.play();
        });

        StackPane root = new StackPane();
        root.getChildren().add(btn);

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

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

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

}
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.