3 Replies Latest reply: May 2, 2013 3:42 PM by jsmith RSS

    Table cell progress indicator

    JGagnon
      I see in the JavaFX 2.2 API that there is a table cell implementation for rendering a progress bar. However, it appears that this only supports the progress indicator that takes an incremental update value (as a double, presumably a value between 0 and 1?) to adjust the "progress" in the indicator. There does not appear to be any support for an indeterminate progress indicator that can be embedded in a table cell.

      I am asking because I have a situation where there will be a background task that will be launched potentially for any row in a table. The task may take some time to execute and complete and I would like to show an indeterminate progress indicator that will "spin" until the task completes. I wanted to be able to include this as a cell (column) for each row so that individual rows could indicate whether they were currently "working" or done.

      Is there, or will there be in the near future, support for this type of functionality? Or, are there suggestions on a way to cobble together something that can do this?
        • 1. Re: Table cell progress indicator
          James_D
          If the value in the cell is negative, then the ProgressBar will be displayed as indeterminate.

          Otherwise you could implement a custom cell factory and set a ProgressBar as the indicator.

          Here's an example of the first technique:
          import java.util.Random;
          import java.util.concurrent.Executor;
          import java.util.concurrent.ExecutorService;
          import java.util.concurrent.Executors;
          import java.util.concurrent.ThreadFactory;
          
          import javafx.application.Application;
          import javafx.concurrent.Task;
          import javafx.scene.Scene;
          import javafx.scene.control.ProgressIndicator ;
          import javafx.scene.control.TableColumn;
          import javafx.scene.control.TableView;
          import javafx.scene.control.cell.ProgressBarTableCell;
          import javafx.scene.control.cell.PropertyValueFactory;
          import javafx.scene.layout.BorderPane;
          import javafx.stage.Stage;
          
          public class ProgressBarTableCellTest extends Application {
          
            @Override
            public void start(Stage primaryStage) {
              TableView<TestTask> table = new TableView<TestTask>();
              Random rng = new Random();
              for (int i = 0; i < 20; i++) {
                table.getItems().add(
                    new TestTask(rng.nextInt(3000) + 2000, rng.nextInt(30) + 20));
              }
          
              TableColumn<TestTask, String> statusCol = new TableColumn<>("Status");
              statusCol.setCellValueFactory(new PropertyValueFactory<TestTask, String>(
                  "message"));
              statusCol.setPrefWidth(75);
          
              TableColumn<TestTask, Double> progressCol = new TableColumn<>("Progress");
              progressCol.setCellValueFactory(new PropertyValueFactory<TestTask, Double>(
                  "progress"));
              progressCol
                  .setCellFactory(ProgressBarTableCell.<TestTask> forTableColumn());
          
              table.getColumns().addAll(statusCol, progressCol);
          
              BorderPane root = new BorderPane();
              root.setCenter(table);
              primaryStage.setScene(new Scene(root));
              primaryStage.show();
          
              ExecutorService executor = Executors.newFixedThreadPool(table.getItems().size(), new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                  Thread t = new Thread(r);
                  t.setDaemon(true);
                  return t;
                }
              });
              
              
              for (TestTask task : table.getItems()) {
                executor.execute(task);
              }
            }
          
            public static void main(String[] args) {
              launch(args);
            }
          
            static class TestTask extends Task<Void> {
          
              private final int waitTime; // milliseconds
              private final int pauseTime; // milliseconds
          
              public static final int NUM_ITERATIONS = 100;
          
              TestTask(int waitTime, int pauseTime) {
                this.waitTime = waitTime;
                this.pauseTime = pauseTime;
              }
          
              @Override
              protected Void call() throws Exception {
                this.updateProgress(ProgressIndicator.INDETERMINATE_PROGRESS, 1);
                this.updateMessage("Waiting...");
                Thread.sleep(waitTime);
                this.updateMessage("Running...");
                for (int i = 0; i < NUM_ITERATIONS; i++) {
                  updateProgress((1.0 * i) / NUM_ITERATIONS, 1);
                  Thread.sleep(pauseTime);
                }
                this.updateMessage("Done");
                this.updateProgress(1, 1);
                return null;
              }
          
            }
          }
          • 2. Re: Table cell progress indicator
            JGagnon
            Pretty slick. This works great for a progress bar (both indeterminate and otherwise). How could I do the same thing with a progress indicator?
            • 3. Re: Table cell progress indicator
              jsmith
              Copy and paste the ProgressBarTableCell code and search and replace ProgressBar with ProgressIndicator on it.

              http://hg.openjdk.java.net/openjfx/8/master/rt/file/tip/javafx-ui-controls/src/javafx/scene/control/cell/ProgressBarTableCell.java

              Here's a minor update to the great example James put together which does this.

              Seems to work fine. Only issue I see when running it on a recent jdk8 version is that the indeterminate progress indicator is a bit too big, then the whole indicator shrinks a little bit once the indicator switches to reporting percentage progress. I didn't investigate that any further, likely it's a minor styling bug in the new JavaFX modena style sheet for Java 8 which you could work around by applying your own style to indeterminate progress indicators.
              /*
               * Portions of this code (ProgressIndicatorTableCell) are base upon Oracle
               * JavaFX code (ProgressBarTableCell), which is subject to the following license term.
               * 
               * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
               * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
               *
               * This code is free software; you can redistribute it and/or modify it
               * under the terms of the GNU General Public License version 2 only, as
               * published by the Free Software Foundation.  Oracle designates this
               * particular file as subject to the "Classpath" exception as provided
               * by Oracle in the LICENSE file that accompanied this code.
               *
               * This code is distributed in the hope that it will be useful, but WITHOUT
               * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
               * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
               * version 2 for more details (a copy is included in the LICENSE file that
               * accompanied this code).
               *
               * You should have received a copy of the GNU General Public License version
               * 2 along with this work; if not, write to the Free Software Foundation,
               * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
               *
               * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
               * or visit www.oracle.com if you need additional information or have any
               * questions.
               */
              
              package test;
              
              import java.util.Random;
              import java.util.concurrent.*;
              
              import javafx.application.Application;
              import javafx.beans.value.ObservableValue;
              import javafx.concurrent.Task;
              import javafx.scene.Scene;
              import javafx.scene.control.*;
              import javafx.scene.control.cell.PropertyValueFactory;
              import javafx.scene.layout.BorderPane;
              import javafx.stage.Stage;
              import javafx.util.Callback;
              
              public class ProgressIndicatorTableCellTest extends Application {
                public void start(Stage primaryStage) {
                  TableView<TestTask> table = new TableView<>();
                  Random rng = new Random();
                  for (int i = 0; i < 20; i++) {
                    table.getItems().add(
                            new TestTask(rng.nextInt(3000) + 2000, rng.nextInt(30) + 20));
                  }
              
                  TableColumn<TestTask, String> statusCol = new TableColumn("Status");
                  statusCol.setCellValueFactory(new PropertyValueFactory<TestTask, String>(
                          "message"));
                  statusCol.setPrefWidth(75);
              
                  TableColumn<TestTask, Double> progressCol = new TableColumn("Progress");
                  progressCol.setCellValueFactory(new PropertyValueFactory<TestTask, Double>(
                          "progress"));
                  progressCol
                          .setCellFactory(ProgressIndicatorTableCell.<TestTask>forTableColumn());
              
                  table.getColumns().addAll(statusCol, progressCol);
              
                  BorderPane root = new BorderPane();
                  root.setCenter(table);
                  primaryStage.setScene(new Scene(root));
                  primaryStage.show();
              
                  ExecutorService executor = Executors.newFixedThreadPool(table.getItems().size(), new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                      Thread t = new Thread(r);
                      t.setDaemon(true);
                      return t;
                    }
                  });
              
              
                  for (TestTask task : table.getItems()) {
                    executor.execute(task);
                  }
                }
              
                public static void main(String[] args) { launch(args); }
              
                static class TestTask extends Task<Void> {
                  private final int waitTime; // milliseconds
                  private final int pauseTime; // milliseconds
                  public static final int NUM_ITERATIONS = 100;
              
                  TestTask(int waitTime, int pauseTime) {
                    this.waitTime = waitTime;
                    this.pauseTime = pauseTime;
                  }
              
                  @Override
                  protected Void call() throws Exception {
                    this.updateProgress(ProgressIndicator.INDETERMINATE_PROGRESS, 1);
                    this.updateMessage("Waiting...");
                    Thread.sleep(waitTime);
                    this.updateMessage("Running...");
                    for (int i = 0; i < NUM_ITERATIONS; i++) {
                      updateProgress((1.0 * i) / NUM_ITERATIONS, 1);
                      Thread.sleep(pauseTime);
                    }
                    this.updateMessage("Done");
                    this.updateProgress(1, 1);
                    return null;
                  }
                }
              }
              
              class ProgressIndicatorTableCell<S> extends TableCell<S, Double> {
                public static <S> Callback<TableColumn<S, Double>, TableCell<S, Double>> forTableColumn() {
                  return new Callback<TableColumn<S, Double>, TableCell<S, Double>>() {
                    @Override
                    public TableCell<S, Double> call(TableColumn<S, Double> param) {
                      return new ProgressIndicatorTableCell<>();
                    }
                  };
                }
              
                private final ProgressIndicator progressIndicator;
                private ObservableValue observable;
              
                public ProgressIndicatorTableCell() {
                  this.getStyleClass().add("progress-indicator-table-cell");
              
                  this.progressIndicator = new ProgressIndicator();
                  setGraphic(progressIndicator);
                }
              
                @Override public void updateItem(Double item, boolean empty) {
                  super.updateItem(item, empty);
              
                  if (empty) {
                    setGraphic(null);
                  } else {
                    progressIndicator.progressProperty().unbind();
              
                    observable = getTableColumn().getCellObservableValue(getIndex());
                    if (observable != null) {
                      progressIndicator.progressProperty().bind(observable);
                    } else {
                      progressIndicator.setProgress(item);
                    }
              
                    setGraphic(progressIndicator);
                  }
                }
              }