简体   繁体   中英

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

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. (eg 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 this related Stackoverflow post and also here and tried to tweak this as needed but got stuck on some issues:

  1. Currently, each ProgressIndicator for each row is displayed immediately (as indeterminate) when I run the program. How can I only activate them / make them visible for each row one after another once the button is pressed?
  2. Pressing the button again once the fake tasks are done does not restart it. How would I have to modify / rebuild the program to make resets possible?
  3. Does the overall approach make sense?

My current runnable code:

import java.util.Random;
import java.util.concurrent.*;

import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
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 < 3; i++) {
            table.getItems().add(new TestTask(rng.nextInt(3000) + 2000, "Test"));
        }

        TableColumn<TestTask, String> nameCol = new TableColumn("Name");
        nameCol.setCellValueFactory(new PropertyValueFactory<TestTask, String>("name"));
        nameCol.setPrefWidth(75);

        TableColumn<TestTask, Double> progressCol = new TableColumn("Progress");
        progressCol.setCellValueFactory(new PropertyValueFactory<TestTask, Double>("progress"));
        progressCol.setCellFactory(ProgressIndicatorTableCell.<TestTask>forTableColumn());

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

        BorderPane root = new BorderPane();
        root.setCenter(table);
        Button btn = new Button("Start");
        btn.setOnAction(actionEvent -> {
            ExecutorService executor = Executors.newSingleThreadExecutor();

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

And the current output:

在此处输入图像描述 在此处输入图像描述

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

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM