首页 > 解决方案 > 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:

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.

标签: javajavafxjava-10

解决方案


可以在此处找到错误报告:JDK-8207837

  • 受影响的版本:910openjfx-11
  • 受影响的平台:通用(全部)。
  • 固定版本:openjfx-12.

原来这Dialog不是问题的根源。该问题是由于在已经添加到ProgressBaraScene之后添加到 a 引起的;这导致问题的原因是JDK-8216377。有关更多更好的解释,请参阅此评论。这是演示该问题的附加测试代码:SceneStage

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

如果你移动到stage.setScene(scene)后将动画。root.getChildren().add(progBar)ProgressBar

基于此信息,我相信使用 a 时的解决方法Dialog是不可能的。ADialog有一个内部Stage用于显示。当在 aDialogPane上设置 a 时,Dialog它会创建 aScene然后将其添加到 this Stage。不幸的是, aDialog是用 a 初始化的DialogPane,因此无论Scene将什么添加到 aStage之前,都可以添加ProgressBar.

为避免此问题,请使用 JavaFX 8 或 OpenJFX 12+。该修复程序可能(或将)向后移植到 OpenJFX 11,但我不确定(如果您知道一种或另一种方法,请发表评论)。


推荐阅读