Skip to content

Instantly share code, notes, and snippets.

@james-d
Last active October 20, 2020 13:27
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save james-d/7722752 to your computer and use it in GitHub Desktop.
Save james-d/7722752 to your computer and use it in GitHub Desktop.
Example of a ListView which displays tasks. The customized ListCell updates its display depending on the state and progress of the task. The Task implementation introduces a counter property: since changing the value of this property will result in changes to the UI, these changes must be performed on the FX Application Thread.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.concurrent.Worker.State;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
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.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ListCellTaskExample extends Application {
@Override
public void start(Stage primaryStage) {
final ExecutorService exec = Executors.newCachedThreadPool(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
return thread;
}
});
final ListView<CountingTask> listView = new ListView<>();
listView.setCellFactory(new Callback<ListView<CountingTask>, ListCell<CountingTask>>() {
@Override
public ListCell<CountingTask> call(ListView<CountingTask> list) {
final ListCell<CountingTask> cell = new ListCell<>();
final ChangeListener<State> stateListener = new ChangeListener<State>() {
@Override
public void changed(ObservableValue<? extends State> observable,
State oldValue, State newValue) {
cell.setGraphic(updateGraphicFromState(cell.getItem(), exec));
}
};
cell.itemProperty().addListener(
new ChangeListener<CountingTask>() {
@Override
public void changed(ObservableValue<? extends CountingTask> observable,
CountingTask oldTask, CountingTask newTask) {
if (oldTask != null) {
oldTask.stateProperty().removeListener(stateListener);
}
if (newTask != null) {
cell.setGraphic(updateGraphicFromState(newTask, exec));
newTask.stateProperty().addListener(stateListener);
}
}
});
cell.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
return cell;
}
});
final BorderPane root = new BorderPane();
root.setCenter(listView);
final Button button = new Button("New Task");
button.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
listView.getItems().add(new CountingTask());
listView.scrollTo(listView.getItems().size() - 1);
}
});
HBox buttonContainer = new HBox();
buttonContainer.setAlignment(Pos.CENTER);
buttonContainer.getChildren().add(button);
root.setBottom(buttonContainer);
final Insets padding = new Insets(10);
buttonContainer.setPadding(padding);
root.setPadding(padding);
final Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private Node updateGraphicFromState(final CountingTask task,final ExecutorService exec) {
if (task == null) { // shouldn't happen...
throw new IllegalArgumentException("Null task");
}
final State state = task.getState();
if (state == State.READY) {
Button button = new Button("Start");
button.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
exec.submit(task);
}
});
return button;
} else if (state == State.SCHEDULED) {
return new Label("Waiting to run");
} else if (state == State.RUNNING) {
final ProgressBar progBar = new ProgressBar();
progBar.progressProperty().bind(task.progressProperty());
final Label label = new Label();
label.textProperty().bind(
Bindings.format("Current count: %d", task.countProperty()));
final Button cancelButton = new Button("Cancel");
cancelButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
task.cancel();
}
});
final HBox hbox = new HBox(5);
hbox.getChildren().addAll(label, progBar, cancelButton);
return hbox;
} else if (state == State.FAILED) {
return new Label("Error");
} else if (state == State.CANCELLED) {
return new Label("Cancelled");
} else if (state == State.SUCCEEDED) {
return new Label("Count finished");
} else {
System.out.println("Unexpected state: " + state);
return null;
}
}
public static void main(String[] args) {
launch(args);
}
public static class CountingTask extends Task<Void> {
private final ReadOnlyIntegerWrapper count = new ReadOnlyIntegerWrapper(this, "count", 0);
private final static int PAUSE = 500; // milliseconds
private final static int MAX_COUNT = 100;
@Override
protected Void call() throws Exception {
for (int i = 1; i <= MAX_COUNT; i++) {
final int newCount = i;
Platform.runLater(new Runnable() {
@Override
public void run() {
count.set(newCount);
}
});
updateProgress(i, MAX_COUNT);
try {
Thread.sleep(PAUSE);
} catch (InterruptedException e) {
if (isCancelled()) {
break;
} else {
throw e; // Interrupted without cancelling
}
}
}
return null;
}
public final int getCount() {
return count.get();
}
public final ReadOnlyIntegerProperty countProperty() {
return count.getReadOnlyProperty();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment