Updating ProgressBar value within a for loop

2019-05-31 06:31发布

I attempted to update a ProgressBar value within a for loop. Its progress should update every 100 miliseconds but unfortunately it didn't work! Here's the code:

pb = new ProgressBar(0);
Button btn = new Button("Start!");
btn.setOnAction(new EventHandler<ActionEvent>(){
    @Override
    public void handle(ActionEvent event) {
        for(int i = 0; i <=100; i++){
            position = i;
            Platform.runLater(new Runnable(){
                @Override
                public void run() {
                    pb.setProgress(position);
                    System.out.println("Index: "+position);
                }
            });
            try{
                Thread.sleep(100);
            }catch(Exception e){ System.err.println(e); }
        }
    }
});

Question 1: I was expecting the above code to update the progressbar every 100 miliseconds but it didn't work. Why?

Question 2: I called System.out.println("Index: "+position); within the loop. I assumed that the output should be 1..2..3..4... but I was wrong. The output is something like this:

Index: 100
Index: 100
Index: 100
Index: 100
Index: 100
Index: 100
Index: 100

2条回答
ら.Afraid
2楼-- · 2019-05-31 06:39

An alternative to re-inventing the wheel by manually calculating and setting the progressProperty of the bar is to use a Task: it has a progressProperty that auto-calculates the relative amount and guarantees to update on the FX-application thread, such that it is safe to bind any scene-related property to.

Something like:

Task<Void> task = new Task<Void>() {

    @Override
    protected Void call() throws Exception {
        double max = 137;
        for (int i = 0; i <= max; i++) {
            updateProgress(i, max);
            Thread.sleep(100);
        }
        return null;
    }

};
button.setOnAction(e -> {
    // a task is not reusable, disable
    button.setDisable(true);
    // bind "late" to not get an initial indeterminate PB
    bar.progressProperty().bind(task.progressProperty());
    new Thread(task).start();
});

Obviously, this is the very barest snippet - see Task for examples that play nicely with cancelling/interrupting the thread.

查看更多
相关推荐>>
3楼-- · 2019-05-31 07:03

Background

  1. Platform.runLater() is called to execute something on the JavaFX Application thread when you are on some other thread. You are running it every 100ms when you are already on the JavaFX application thread adding all these items in queue, because the thread is already busy running the loop and sleeping :)

    So it runs everything in queue after the thread is free to execute them i.e. after finishing the loop.

  2. There is another problem, ProgressBar has a progressproperty whose value can be between 0 and 1, where 0 represents 0% and 1 represents 100%. If you want to update the progress bar, you would want to set the progress with values between 0.1 to 1.0.

How to FIX

You should start a new Thread on the button's action and let it handle the loop. You can use Platform.runLater inside this thread, without choking the JavaFX application thread.

I would run something as below :

btn.setOnAction(event -> {
    new Thread(() -> {
         for(int i = 0; i <=100; i++){
            final int position = i;
            Platform.runLater(() -> {
                pb.setProgress(position/100.0);
                System.out.println("Index: " + position);
            });
            try{
                Thread.sleep(100);
            }catch(Exception e){ System.err.println(e); }
         }
    }).start();
});

Note : This answer stresses more on where the OP went wrong and how he can rectify it. If you are on JavaFX, you would want to use better approaches i.e. using Task and its updateProgress() for updating the progress of the ProgressBar as stated by @kleopatra in her solution.

查看更多
登录 后发表回答