0

I have a JavaFX application that checks for the presence of a database at startup. If the database is not found, a "seed" database must be created before the program can proceed.

Since creating the seed database can be lengthy, I want to display a splash screen and show progress updates as the process proceeds. Only after the seed database is completely written should the program proceed.

Here is the class for the splash screen:

public final class SplashWindow {

    private final Label message;
    private final ProgressBar progress;
    private final Stage splashStage;

    public SplashWindow() {
        Image img = new Image(IMAGE_PREFIX + "splash_image.png");
        double imgWidth = img.getWidth();
        ImageView splashImage = new ImageView(img);
        splashImage.setFitWidth(imgWidth);
        splashImage.setPreserveRatio(true);

        message = new Label("Saving seed database...");
        message.setPrefWidth(imgWidth);
        message.setAlignment(Pos.CENTER);

        progress = new ProgressBar();
        progress.setPrefWidth(imgWidth);

        Pane splashVBox = new VBox(3);
        splashVBox.setPadding(new Insets(5));
        splashVBox.getChildren().addAll(splashImage, progress, message);

        splashStage = new Stage(StageStyle.UTILITY);
        splashStage.setScene(new Scene(splashVBox));
    }

    public void bindMessageProperty(ReadOnlyStringProperty sp) {
        message.textProperty().bind(sp);
    }

    public void bindProgressProperty(ReadOnlyDoubleProperty dp) {
        progress.progressProperty().bind(dp);
    }

    public void show() {
        splashStage.show();
    }

    public void shutdown() {
        message.textProperty().unbind();
        progress.progressProperty().unbind();
        splashStage.hide();
    }
}

When run, the splash screen shows correctly with the image, progress bar and text message area.

The SplashWindow class is called by the following method:

private void saveSeedDatabase(ObservableList<RefModel> docList) {
    SplashWindow splash = new SplashWindow();

    Task<Integer> saveTask = new Task<Integer>() {
        @Override
        protected Integer call() throws InterruptedException {
            updateMessage("Saving references...");
            int docsSaved = 0;
            for (RefModel rm : docList) {
                if (isCancelled()) {
                    updateMessage("Cancelled");
                    break;
                }

                updateMessage("Saving: " + rm.getTitle());
                saveNewReference(rm);
                docsSaved++;
                updateProgress(docsSaved, docList.size());
            }
            updateMessage("Saved " + docsSaved + " references to database");
            return docsSaved;
        }
    };
    saveTask.setOnSucceeded((WorkerStateEvent t) -> {
        splash.shutdown();
    });

    splash.bindMessageProperty(saveTask.messageProperty());
    splash.bindProgressProperty(saveTask.progressProperty());
    splash.show();

    new Thread(saveTask).start();
    try {
        saveTask.get();
    } catch (InterruptedException | ExecutionException ex) {
        // Do nothing
    }
}

When run, the splash screen is displayed but never shows the update messages and progress. It shows a wait cursor when the mouse if over the splash screen.

If the try/catch block at the end of the method is commented out, the main program attempts to proceed without waiting for the database to be written. In this case, the splash screen is hidden by the main program window, but does display the update messages as it works. From debugging this, it looks like everything is running on the correct thread -- the database stuff is on a worker thread, the SplashWindow stuff is on the FX event thread.

It seems clear that the call to saveTask.get() is blocking the UI thread, but I am not sure why.

@JewelSea has written a nice alternative that doesn't fit my program architecture very well. It would not be impossible to alter my program to work with his solution.

However, I don't understand why the get() call blocks the UI. What am I doing wrong.

Community
  • 1
  • 1
clartaq
  • 4,902
  • 3
  • 35
  • 44

1 Answers1

2

According to the JavaDocs:

(get()) Waits if necessary for the computation to complete, and then retrieves its result.

Retrieving the computed value without blocking the GUI Thread would be done with getValue(). However: This method only returns a result, when the Task has successfully finished its work. That is why you should do this aysnc in the onSucceeded block.

eckig
  • 10,174
  • 4
  • 33
  • 48
  • No, using the `getValue()` method still allows the program to proceed before the database is written. I want to prevent the `saveSeedDatabase()` method from returning until the task is complete and the database is written, but without freezing the updates to the splash screen. – clartaq Jun 18 '15 at 14:20
  • 2
    The point is you call `saveSeedDatabase` on the FX Application Thread (which you must, because it creates and shows a `Stage`). So if you block in that method ("prevent the method from returning until the task is complete") you are necessarily blocking the FX Application Thread. Anything you need to do after the task completes must be done in the `onSucceeded` handler. – James_D Jun 18 '15 at 14:42