Last active
October 20, 2020 13:27
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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