Home > other >  How to put a ProcessIndicator in each row of a TableView and indicate task status
How to put a ProcessIndicator in each row of a TableView and indicate task status

Time:01-12

I'm trying to do the following in JavaFX:

  • Have a TableView with multiple rows.
  • Each row contains columns with text and one Progress/Status column.
  • When a specific Button is pressed, for each row of the TableView some task should be performed, one row after the other. (e.g. check some data, ...)
  • While this task is performed, a indeterminate ProgressIndicator shall be shown in the Status column, until the task for this row is finished, then the indicator shows as done.
  • When all tasks for each row are done, the button can be pressed again to reset the status and execute the tasks again.

I had found some help in enter image description here enter image description here

CodePudding user response:

Here is a version that implements your first question. With this requirement, the cell is only a function of the task's state. If it's RUNNING, display an indeterminate progress indicator; if it's SUCCEEDED display a progress indicator with value 1; otherwise, display nothing.

Note the original question is very old and uses a lot of outdated code styles. I've updated accordingly.

import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ProgressIndicatorTableCellTest extends Application {
    public void start(Stage primaryStage) {
        TableView<TestTask> table = new TableView<>();
        Random rng = new Random();
        for (int i = 0; i < 3; i  ) {
            table.getItems().add(new TestTask(rng.nextInt(3000)   2000, "Test"));
        }

        TableColumn<TestTask, String> nameCol = new TableColumn<>("Name");
        nameCol.setCellValueFactory(data -> data.getValue().nameProperty());
        nameCol.setPrefWidth(75);

        TableColumn<TestTask, Worker.State> progressCol = new TableColumn<>("Progress");
        progressCol.setCellValueFactory(data -> data.getValue().stateProperty());
        progressCol.setCellFactory(col -> new ProgressIndicatorTableCell<>());

        table.getColumns().addAll(nameCol, progressCol);

        BorderPane root = new BorderPane();
        root.setCenter(table);
        Button btn = new Button("Start");
        btn.setOnAction(actionEvent -> {
            ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
                Thread t = new Thread(r);
                t.setDaemon(true);
                return t;
            });

            for (TestTask task : table.getItems()) {
                executor.submit(task);
            }
        });

        root.setBottom(btn);
        primaryStage.setScene(new Scene(root));
        primaryStage.show();

    }

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

    public static class TestTask extends Task<Void> {
        private final int waitTime; // milliseconds
        final ReadOnlyStringWrapper name = new ReadOnlyStringWrapper();
        public static final int NUM_ITERATIONS = 100;

        public TestTask(int waitTime, String name) {
            this.waitTime = waitTime;
            this.name.set(name);
        }

        public ReadOnlyStringProperty nameProperty() {
            return name.getReadOnlyProperty();
        }

        @Override
        protected Void call() throws Exception {
            this.updateProgress(ProgressIndicator.INDETERMINATE_PROGRESS, 1);
            Thread.sleep(waitTime);
            this.updateProgress(1, 1);
            return null;
        }
    }
}

class ProgressIndicatorTableCell<S> extends TableCell<S, Worker.State> {

    private final ProgressIndicator progressIndicator = new ProgressIndicator();

    @Override
    protected void updateItem(Worker.State state, boolean empty) {
        super.updateItem(state, empty);
        if (state == Worker.State.SUCCEEDED) {
            progressIndicator.setProgress(1);
            setGraphic(progressIndicator);
        } else if (state == Worker.State.RUNNING) {
            progressIndicator.setProgress(-1);
            setGraphic(progressIndicator);
        } else {
            setGraphic(null);
        }
    }

}

To allow for "restarting", you should use a Service instead of just a Task. This version will allow for a restart if the button is pressed multiple times, returning everything to the initial state before proceeding.

This version also factors the processing work out of the model class, which is desirable for properly assigning responsibilities to classes:

Item.java:

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleObjectProperty;

public class Item {

    public enum State {WAITING, PROCESSING, READY}

    final ReadOnlyStringWrapper name = new ReadOnlyStringWrapper();

    private final ObjectProperty<State> state = new SimpleObjectProperty<>(State.WAITING);


    public Item(String name) {
        this.name.set(name);
    }

    public ReadOnlyStringProperty nameProperty() {
        return name.getReadOnlyProperty();
    }

    public State getState() {
        return state.get();
    }

    public ObjectProperty<State> stateProperty() {
        return state;
    }

    public void setState(State state) {
        this.state.set(state);
    }
}

ProcessManager.java:

import javafx.application.Platform;
import javafx.concurrent.Service;
import javafx.concurrent.Task;

import java.util.List;
import java.util.Random;

public class ProcessManager {

    private final List<Item> items;

    private Random rng = new Random();

    private Service<Void> service = new Service<>() {

        @Override
        protected Task<Void> createTask() {
            return new Task<>() {
                @Override
                protected Void call() throws Exception {
                    for (Item task: items) {
                        try {
                            Platform.runLater(() -> task.setState(Item.State.PROCESSING));
                            Thread.sleep(2000   rng.nextInt(3000));
                            Platform.runLater(() -> task.setState(Item.State.READY));
                        } catch (InterruptedException exc) {
                            Thread.currentThread().interrupt();
                        }
                        if (isCancelled()) {
                            Platform.runLater(() -> task.setState(Item.State.WAITING));
                            break;
                        }
                    }
                    return null;
                }
            };
        }
    };


    public ProcessManager(List<Item> items) {
        this.items = items ;
        service.setOnCancelled(e -> items.forEach(task -> task.setState(Item.State.WAITING)));
    }


    public void process() {
        service.restart();
    }
}

and the application:

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class ProgressIndicatorTableCellTest extends Application {

    public void start(Stage primaryStage) {

        ObservableList<Item> tasks = FXCollections.observableArrayList();

        ProcessManager processManager = new ProcessManager(tasks);

        TableView<Item> table = new TableView<>();
        for (int i = 0; i < 3; i  ) {
            Item task = new Item("Item "   (i   1));
            tasks.add(task);
        }
        table.setItems(tasks);

        TableColumn<Item, String> nameCol = new TableColumn<>("Name");
        nameCol.setCellValueFactory(data -> data.getValue().nameProperty());
        nameCol.setPrefWidth(75);

        TableColumn<Item, Item.State> progressCol = new TableColumn<>("Progress");
        progressCol.setCellValueFactory(data -> data.getValue().stateProperty());
        progressCol.setCellFactory(col -> new TableCell<>() {
            private final ProgressIndicator indicator = new ProgressIndicator();
            @Override
            protected void updateItem(Item.State state, boolean empty) {
                super.updateItem(state, empty);
                if (state == Item.State.PROCESSING) {
                    indicator.setProgress(-1);
                    setGraphic(indicator);
                } else if (state == Item.State.READY) {
                    indicator.setProgress(1);
                    setGraphic(indicator);
                } else {
                    setGraphic(null);
                }
            }
        });

        table.getColumns().addAll(nameCol, progressCol);

        BorderPane root = new BorderPane();
        root.setCenter(table);
        Button btn = new Button("Start");
        btn.setOnAction(actionEvent -> processManager.process());

        root.setBottom(btn);
        primaryStage.setScene(new Scene(root));
        primaryStage.show();

    }

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

}
  • Related