Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Indeterminate ProgressBar does not animate when part of a Dialog (JavaFX 10)

When a ProgressBar is indeterminate it has an animation that goes back and forth. This works fine when the ProgressBar is part of a normal Stage but doesn't work when part of a Dialog. Instead, it seems to just sit at the start of the animation. The ProgressBar does, however, update properly when set to some determinate value. Note: The issue does not appear in Java 8.

There are no indications of any exceptions.

Here's an MCVE (GIF of it in action):

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class App extends Application {

    @Override
    public void start(Stage primaryStage) {
        Button detButton = new Button("Launch Determinate Task");
        detButton.setOnAction(ae -> {
            ae.consume();
            createDialog(primaryStage, true)
                    .showAndWait();
        });

        Button indetButton = new Button("Launch Indeterminate Task");
        indetButton.setOnAction(ae -> {
            ae.consume();
            createDialog(primaryStage, false)
                    .showAndWait();
        });

        HBox btnBox = new HBox(detButton, indetButton);
        btnBox.setSpacing(10);
        btnBox.setAlignment(Pos.CENTER);

        StackPane root = new StackPane(btnBox, createDummyProgressNode());

        Scene scene = new Scene(root, 500, 300);
        primaryStage.setScene(scene);
        primaryStage.setTitle("ProgressBar Issue");
        primaryStage.setResizable(false);
        primaryStage.show();
    }

    private Node createDummyProgressNode() {
        Label label = new Label("ProgressBar to show animation in Stage.");

        ProgressBar progressBar = new ProgressBar();
        progressBar.setMaxWidth(Double.MAX_VALUE);

        VBox box = new VBox(label, progressBar);
        box.setMaxHeight(VBox.USE_PREF_SIZE);
        box.setSpacing(3);
        box.setAlignment(Pos.CENTER_LEFT);
        box.setPadding(new Insets(5));

        StackPane.setAlignment(box, Pos.BOTTOM_CENTER);

        return box;
    }

    private Dialog<?> createDialog(Stage owner, boolean determinate) {
        Task<?> task = new BackgroundTask(determinate);

        Dialog<?> dialog = new Dialog<>();
        dialog.initOwner(owner);
        dialog.setTitle("Background Task - " 
                + (determinate ? "Determinate" : "Indeterminate"));

        dialog.getDialogPane().setPrefWidth(300);
        dialog.getDialogPane().setContent(createDialogContent(task));

        dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
        dialog.getDialogPane().lookupButton(ButtonType.OK)
                .disableProperty().bind(task.runningProperty());

        dialog.setOnShown(de -> {
            de.consume();
            executeTask(task);
        });

        return dialog;
    }

    private Node createDialogContent(Task<?> task) {
        Label label = new Label();
        label.textProperty().bind(task.messageProperty());

        ProgressBar progressBar = new ProgressBar();
        progressBar.setMaxWidth(Double.MAX_VALUE);
        progressBar.progressProperty().bind(task.progressProperty());

        VBox box = new VBox(label, progressBar);
        box.setSpacing(3);
        box.setAlignment(Pos.CENTER_LEFT);

        return box;
    }

    private void executeTask(Task<?> task) {
        Thread thread = new Thread(task, "background-thread");
        thread.setDaemon(true);
        thread.start();
    }

    private static class BackgroundTask extends Task<Void> {

        private final boolean determinate;

        private BackgroundTask(boolean determinate) {
            this.determinate = determinate;
        }

        @Override
        protected Void call() throws Exception {
            final int loops = 1_000;
            for (int i = 0; i <= loops; i++) {
                updateMessage("Running... " + i);
                Thread.sleep(1L);
                if (determinate) {
                    updateProgress(i, loops);
                }
            }
            updateMessage("Complete");
            updateProgress(loops, loops);
            return null;
        }

    }

}

I tried this code on:

  • JDK 1.8.0_181
    • Animates normally
  • JDK 10.0.2
    • Fails to animate
  • OpenJDK 11-ea+22 with OpenJFX 11-ea+18
    • Fails to animate
  • OpenJDK 12-ea with OpenJFX 11-ea+18
    • Fails to animate
    • Didn't really expect a change here since the same JavaFX version is being used.

All my tests were on a Windows 10 Home (version 1803) computer.

I'm 90% certain this is some type of regression bug, but just in case...

  1. Does anyone else have this problem? Or am I doing something wrong and it's a fluke that it works on Java 8?
  2. If it is a bug does anyone know of any workarounds?

I've tried things like changing the Modality and owner of the Dialog to no effect. It also seems to only be the ProgressBar as the Dialog can still be moved around and the message updates (i.e. the UI is not freezing).

I haven't tried this with other animated Nodes such as ProgressIndicator.

P.S. I searched the Java bug database for this or similar issue but came up with nothing; I could have easily missed one, however.

Update: I have submitted my own bug report.

like image 238
Slaw Avatar asked Jul 18 '18 02:07

Slaw


1 Answers

The bug report can be found here: JDK-8207837.

  • Affected Versions: 9, 10, and openjfx-11.
  • Affected Platforms: Generic (all).
  • Fixed Version: openjfx-12.

Turns out the Dialog wasn't the root of the problem. The issue is caused by adding the ProgressBar to a Scene after the Scene was already added to a Stage; the reason this causes a problem is because of JDK-8216377. See this comment for more a better explanation. And here is the attached test code demonstrating the issue:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class IndeterminateProgressBar extends Application {

    @Override
    public void start(Stage stage) {
        StackPane root = new StackPane();

        Scene scene = new Scene(root, 300, 150);
        stage.setScene(scene);

        // If the progress bar is added to the root after the root is
        // added to the scene and the scene is added to the stage, it will
        // fail to be shown.
        ProgressBar progBar = new ProgressBar();
        progBar.setProgress(-1);
        root.getChildren().add(progBar);

        stage.show();
    }

    public static void main(String[] args) {
        Application.launch(args);
    }
}

If you move stage.setScene(scene) to after root.getChildren().add(progBar) the ProgressBar will animate.

Based on this information, I believe a workaround when using a Dialog is impossible. A Dialog has an internal Stage that it uses for display. When a DialogPane is set on the Dialog it causes the creation of a Scene which is then added to this Stage. Unfortunately, a Dialog is initialized with a DialogPane and so no matter what the Scene will have been added to a Stage before one can add the ProgressBar.

To avoid the issue use JavaFX 8 or OpenJFX 12+. It's possible the fix is (or will be) backported to OpenJFX 11 but I'm not sure (please leave a comment if you know one way or the other).

like image 139
Slaw Avatar answered Oct 12 '22 14:10

Slaw