JavaFX MultiThreading Flashing Lights - javafx

I have two circles redCircle and greenCircle : -
Circle greenCircle = new Circle(250,150 ,100, Color.TRANSPARENT);
Circle redCircle = new Circle(250,450,100,Color.TRANSPARENT);
greenCircle.setStroke(Color.GREEN);
greenCircle.setStrokeWidth(4);
group.getChildren().add(greenCircle);
redCircle.setStroke(Color.RED);
redCircle.setStrokeWidth(4);
group.getChildren().add(redCircle);
Basically I want Circle to turn on and off twice in 2 seconds. So I am able to turn on Light then wait for 0.5 second , turn off and again wait for 0.5 second and turn on Light. I am not about to turn off after 0.5 second.
public class LightOn {
public Task<Void> runLightOn() throws InterruptedException {
return new Task<Void>(){
#Override
protected Void call() throws Exception {
greenCircle.setFill(Color.GREEN);
return null;
}
};
}
}
public class LightOff {
public void perform() throws InterruptedException {
LightOn onL = new LightOn();
Task<Void> runLinghtOnTask = onL.runLightOn();
runLinghtOnTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
Logger.getLogger(MovementEventsDemo.class.getName()).log(Level.SEVERE, null, ex);
}
greenCircle.setFill(Color.TRANSPARENT);
nextFunction();
}
});
new Thread(runLinghtOnTask).start();
}
}
public void nextFunction(){
Task<Void> sleeper2 = new Task<Void>() {
#Override
protected Void call() throws Exception {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
return null;
}
};
sleeper2.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
if(greenCircle.getFill()==Color.GREEN) {
greenCircle.setFill(Color.TRANSPARENT);
}else {
greenCircle.setFill(Color.GREEN);
}
}
});
new Thread(sleeper2).start();
}
I use this to execute : -
LightOff lf = new LightOff();
lf.perform();

The reason your code is not working is that an exception is being thrown in the call() method of the Task returned by runLightOn(), because you are changing the UI from a background thread. If you catch the exception or register an onFailed handler with the Tasks you will be able to log the exception.
For functionality like this, where you have specific timepoints (KeyFrames) at which you want values to change, use a Timeline instead of messing with multithreading:
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class FlashingLight extends Application {
#Override
public void start(Stage primaryStage) {
Circle circle = new Circle(250, 150, 100, Color.TRANSPARENT);
circle.setStroke(Color.GREEN);
circle.setStrokeWidth(4);
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0.5), e -> circle.setFill(Color.GREEN)),
new KeyFrame(Duration.seconds(1.0), e -> circle.setFill(Color.TRANSPARENT))
);
timeline.setCycleCount(2);
Pane pane = new Pane(circle);
Scene scene = new Scene(pane, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
timeline.play();
}
public static void main(String[] args) {
launch(args);
}
}
As a slight alternative, you can replace
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0.5), e -> circle.setFill(Color.GREEN)),
new KeyFrame(Duration.seconds(1.0), e -> circle.setFill(Color.TRANSPARENT))
);
with
BooleanProperty on = new SimpleBooleanProperty();
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0.5), new KeyValue(on, true)),
new KeyFrame(Duration.seconds(1.0), new KeyValue(on, false))
);
circle.fillProperty().bind(
Bindings.when(on)
.then(Color.GREEN)
.otherwise(Color.TRANSPARENT));
The added benefits here are that you create a boolean value representing whether or not the light is on, which may be useful elsewhere in your logic, and that you separate the logic (on/off, and the timing) from the display (color, etc).

Related

JavaFX progress indicator stop spinning when heavy load runs

I am trying to implement busy indicator using ProgressIndicator. But when the heavy load starts the indicator freezes. A sample code is shown below.
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class BusyIcon {
private static Stage busyWindow;
public static void showBusyIcon(final Stage stage) {
busyWindow = new Stage(StageStyle.UNDECORATED);
//busyWindow.setOpacity(.3);
busyWindow.initOwner(stage);
busyWindow.initModality(Modality.WINDOW_MODAL);
StackPane stackPane = new StackPane();
final ProgressIndicator loadingIndicator = new ProgressIndicator();
loadingIndicator.setVisible(true);
stackPane.getChildren().add(loadingIndicator);
Scene scene = new Scene(stackPane, 100, 100);
scene.setFill(Color.TRANSPARENT);
busyWindow.setScene(scene);
ChangeListener<Number> widthListener = (observable, oldValue, newValue) -> {
double stageWidth = newValue.doubleValue();
busyWindow.setX(stage.getX() + stage.getWidth() / 2 - stageWidth / 2);
};
ChangeListener<Number> heightListener = (observable, oldValue, newValue) -> {
double stageHeight = newValue.doubleValue();
busyWindow.setY(stage.getY() + stage.getHeight() / 2 - stageHeight / 2);
};
busyWindow.widthProperty().addListener(widthListener);
busyWindow.heightProperty().addListener(heightListener);
busyWindow.setOnShown(e -> {
busyWindow.widthProperty().removeListener(widthListener);
busyWindow.heightProperty().removeListener(heightListener);
});
busyWindow.show();
}
public static void closeBusyIcon(final Stage stage) {
if (busyWindow != null) {
busyWindow.close();
busyWindow = null;
}
}
}
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import preloader.BusyIcon;
public class QuestionExample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Task Progress Tester");
StackPane testPane = new StackPane();
Button b = new Button("Load");
b.setOnAction((event) -> {
BusyIcon.showBusyIcon(primaryStage);
Task t = new Task() {
#Override
protected Object call() throws Exception {
try {
addNewComponent(testPane);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
};
t.setOnSucceeded((ev) -> {
BusyIcon.closeBusyIcon(primaryStage);
});
new Thread(t).start();
});
testPane.getChildren().add(b);
primaryStage.setScene(new Scene(testPane, 300, 250));
primaryStage.show();
}
private void addNewComponent(Pane testPane) {
try {
/**
* Some heavy load work will run here
*/
Thread.sleep(2000);
Platform.runLater(() -> {
try {
/**
* We need to change the fx controls here
*/
Button b1 = new Button("New Component");
testPane.getChildren().add(b1);
/**
* This may take some time
*/
Thread.sleep(2000);
} catch (Exception ex) {
ex.printStackTrace();
}
});
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
BusyIcon is used for showing progress indicator. If we are not using the Platform.runLater then it will throw 'Not in FX thread' exception will be thrown.
I suggest you try ControlsFX MaskerPane. The key is to set the MaskerPane visible and move it to the front of an AnchorPane before the task runs. When the task finishes, set it invisible and move it to the back of the AnchorPane.
DEMO:
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.controlsfx.control.MaskerPane;
/**
*
* #author blj0011
*/
public class MaskerPaneTest extends Application
{
#Override
public void start(Stage primaryStage)
{
MaskerPane mpDeterminate = new MaskerPane();
MaskerPane mpUndeterminate = new MaskerPane();
mpDeterminate.setVisible(false);
mpUndeterminate.setVisible(false);
Button btn = new Button();
btn.setText("Determinate");
btn.setOnAction((ActionEvent event) -> {
mpDeterminate.setVisible(true);
mpDeterminate.toFront();
Task<Void> task = new Task<Void>()
{
#Override
protected Void call() throws Exception
{
for (int i = 0; i < 40000000; i++) {
//Do something
updateProgress(i, 40000000);
}
return null;
}
};
mpDeterminate.progressProperty().bind(task.progressProperty());
task.setOnSucceeded((workerStateEvent) -> {
mpDeterminate.setVisible(false);
mpDeterminate.toBack();
});
new Thread(task).start();
});
Button btn2 = new Button();
btn2.setText("Undeterminate");
btn2.setOnAction((ActionEvent event) -> {
mpUndeterminate.setVisible(true);
mpUndeterminate.toFront();
Task<Void> task = new Task<Void>()
{
#Override
protected Void call() throws Exception
{
for (int i = 0; i < 100000; i++) {
//Do something
System.out.println("working!");
}
return null;
}
};
mpUndeterminate.progressProperty().bind(task.progressProperty());
task.setOnSucceeded((workerStateEvent) -> {
mpUndeterminate.setVisible(false);
mpUndeterminate.toBack();
});
new Thread(task).start();
});
StackPane root = new StackPane(mpDeterminate, mpUndeterminate, new VBox(btn, btn2));
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}

How to delay setProgress in Progress Bar in 0.1 steps until full

I am trying to make a progressBar, that will set its progress in 0.1 steps with a delay of 1000ms until it is "full".
I already found a solution how to delay one step, but cannot get it into a for loop that will set the progress in 0.1 steps until the progress equals 1 and therefore is full.
How do I need to modify the solution below to achieve that?
package project;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Progress extends Application {
StackPane stack = new StackPane();
Scene scene = new Scene(stack, 400, 800);
// Progress Bar
ProgressBar progressBar = new ProgressBar();
public void start(Stage primaryStage) throws Exception {
// Progress Bar
stack.getChildren().add(progressBar);
progressBar.setTranslateX(0);
progressBar.setTranslateY(0);
progressBar.setProgress(0);
Task<Void> sleeper = new Task<Void>() {
#Override
protected Void call() throws Exception {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
return null;
}
};
sleeper.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
progressBar.setProgress(0.1);
}
});
new Thread(sleeper).start();
primaryStage.setScene(scene);
primaryStage.setTitle("Title");
primaryStage.show();
}
public static void main(String[] args) {
launch();
}
}
Make your task perform the iteration, and update it's progress as it goes:
Task<Void> sleeper = new Task<Void>() {
#Override
protected Void call() throws Exception {
final int numIterations = 10 ;
for (int i = 0 ; i < numIterations ; i++) {
updateProgress(i, numIterations);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
updateProgress(numIterations, numIterations);
return null;
}
};
Then just bind the progress bar's progress to the task's progress:
progressBar.progressProperty().bind(sleeper.progressProperty());

javafx catch more then one key press event at the same time

I want to catch the event when we press in 2 keys of keyboard (ctrl + here)
to zoom in tabview here's my code, so far i can just catch only the ctrl, i dont know how to catch the event when we hold ctrl then click on + ( or at least click on ctrl then c every time to zoom) , i had idea of key combination:
final KeyCombination keyCtrlPlus = new KeyCodeCombination(KeyCode.PLUS, KeyCombination.CONTROL_ANY);
but i don't know how to do it in the addEventFilter(). Any help please?
m_TabView.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent e)
{
if (keyCtrlPlus.match(e))
{
//function to zoom tabview
zoomOut(e);
}
}
});
This is how I did it. It's a little simpler, in my opinion. Not to mention, this specifically answers Coeur's question; which was, how to check KeyCombination inside of an addEventFilter method ...
This is in my controller class...
#FXML private TextField textField;
final KeyCombination keyShiftTab = new KeyCodeCombination(KeyCode.TAB, KeyCombination.SHIFT_ANY);
#FXML
public void initialize()
{
textField.addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>()
{
#Override
public void handle(KeyEvent e)
{
if (keyShiftTab.match(e))
{
doSomthing();
e.consume();
}
}
});
}
Works like a champ for me. I hope this helps.
You could add a listener, register all key presses of the key pressed event in a bitset and evaluate and unregister them in the key released event.
Something like this, supports multiple keys including modifiers:
import java.util.BitSet;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Main extends Application {
private BitSet keyboardBitSet = new BitSet();
Scene scene;
Label label;
#Override
public void start(Stage primaryStage) {
HBox root = new HBox();
label = new Label();
root.getChildren().add(label);
scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
scene.addEventFilter(KeyEvent.KEY_PRESSED, keyPressedEventHandler);
scene.addEventFilter(KeyEvent.KEY_RELEASED, keyReleasedEventHandler);
// init label text
updateKeyboardStatus();
}
/**
* "Key Pressed" handler for all input events: register pressed key in the bitset
*/
private EventHandler<KeyEvent> keyPressedEventHandler = new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
// register key down
keyboardBitSet.set(event.getCode().ordinal(), true);
updateKeyboardStatus();
}
};
/**
* "Key Released" handler for all input events: unregister released key in the bitset
*/
private EventHandler<KeyEvent> keyReleasedEventHandler = new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
// register key up
keyboardBitSet.set(event.getCode().ordinal(), false);
updateKeyboardStatus();
}
};
/**
* Detect all keys and show them in the label
*/
private void updateKeyboardStatus() {
StringBuilder sb = new StringBuilder();
sb.append("Current key combination: ");
int count = 0;
for( KeyCode keyCode: KeyCode.values()) {
if( keyboardBitSet.get(keyCode.ordinal())) {
if( count > 0) {
sb.append( " ");
}
sb.append(keyCode.toString());
count++;
}
}
label.setText(sb.toString());
}
public static void main(String[] args) {
launch(args);
}
}
Maybe this could help you:
this.scene.addEventHandler(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if(key.length()>0){
if(key.toString().equals(keyCombination1)){
System.out.println("Key Combination 1 pressed");
}else if(key.toString().equals(keyCombination2)){
System.out.println("Key Combination 2 pressed");
}else if(key.toString().equals(keyCombination3)){
System.out.println("Key Combination 3 pressed");
}
key.setLength(0);
}
}
});
Taken from:
https://code.google.com/p/javafx-demos/source/browse/trunk/javafx-demos/src/main/java/com/ezest/javafx/demogallery/KeyCombinationDemo.java

Multiple independent stages in JavaFX

Is there a way to launch multiple independent stages in JavaFX? By independent I mean that the stages are all created from the main thread.
At the moment my application is more or less an algorithm where I would like to plot some charts and tables during execution (mainly to check whether the results are correct/ to debug).
The problem is that I cannot figure out how to create and show multiple stages independently, i.e. I would like to do something like this
public static void main(){
double[] x = subfunction.dosomething();
PlotUtil.plot(x); //creates a new window and shows some chart/table etc.
double[] y = subfunction.dosomethingelse();
PlotUtil.plot(y); //creates a new window and shows some chart/table etc.
.....
}
which would allow to use PlotUtil as one would use the plotting functions in other scripting languages (like Matlab or R).
So the main question is how to "design" PlotUtils? So far I tried two things
PlotUtils uses Application.launch for each plot call (creating a new stage with a single scene every time) --> does not work as Application.launch can only be invoked once.
Create some kind of "Main Stage" during the first call to PlotUtils, get a reference to the created Application and start subsequent stages from there --> does not work as using Application.launch(SomeClass.class) I am not able to get a reference to the created Application instance.
What kind structure/design would allow me to implement such a PlotUtils function?
Update 1:
I came up with the following idea and was wondering whether there are any major mistakes in this solution.
Interface to be implemented by all "Plots"
public abstract class QPMApplication implements StageCreator {
#Override
public abstract Stage createStage();
}
Plotting functionality:
public class PlotStage {
public static boolean toolkitInialized = false;
public static void plotStage(String title, QPMApplication stageCreator) {
if (!toolkitInialized) {
Thread appThread = new Thread(new Runnable() {
#Override
public void run() {
Application.launch(InitApp.class);
}
});
appThread.start();
}
while (!toolkitInialized) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Platform.runLater(new Runnable() {
#Override
public void run() {
Stage stage = stageCreator.createStage();
stage.show();
}
});
}
public static class InitApp extends Application {
#Override
public void start(final Stage primaryStage) {
toolkitInialized = true;
}
}
}
Using it:
public class PlotStageTest {
public static void main(String[] args) {
QPMApplication qpm1 = new QPMApplication() {
#Override
public Stage createStage() {
Stage stage = new Stage();
StackPane root = new StackPane();
Label label1 = new Label("Label1");
root.getChildren().add(label1);
Scene scene = new Scene(root, 300, 300);
stage.setTitle("First Stage");
stage.setScene(scene);
return stage;
}
};
PlotStage.plotStage(qpm1);
QPMApplication qpm2 = new QPMApplication() {
#Override
public Stage createStage() {
Stage stage = new Stage();
StackPane root = new StackPane();
Label label1 = new Label("Label2");
root.getChildren().add(label1);
Scene scene = new Scene(root, 300, 200);
stage.setTitle("Second Stage");
stage.setScene(scene);
return stage;
}
};
PlotStage.plotStage(qpm2);
System.out.println("Done");
}
}
The easiest approach here would be just to refactor your application so that it is driven from the FX Application thread. For example, you could rewrite your original code block as
public class Main extends Application {
#Override
public void start(Stage primaryStageIgnored) {
double[] x = subfunction.dosomething();
PlotUtil.plot(x); //creates a new window and shows some chart/table etc.
double[] y = subfunction.dosomethingelse();
PlotUtil.plot(y); //creates a new window and shows some chart/table etc.
// .....
}
public static void main(String[] args) {
launch(args);
}
}
Now PlotUtil.plot(...) merely creates a Stage, puts a Scene in it, and show()s it.
This assumes the methods you're calling don't block, but if they do you just have to wrap them in a Task and call PlotUtils.plot(...) in the onSucceeded handler for the task.
If you really want to drive this from a non-JavaFX application, there's a fairly well-known hack to force the JavaFX Application thread to start if it's not already started, by creating a new JFXPanel. A JFXPanel should be created on the AWT event dispatch thread.
Here's a very basic example of the second technique. Start the application and type "show" into the console. (Type "exit" to exit.)
import java.util.Scanner;
import java.util.concurrent.FutureTask;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javax.swing.SwingUtilities;
public class Main {
private JFXPanel jfxPanel ;
public void run() throws Exception {
boolean done = false ;
try (Scanner scanner = new Scanner(System.in)) {
while (! done) {
System.out.println("Waiting for command...");
String command = scanner.nextLine();
System.out.println("Got command: "+command);
switch (command.toLowerCase()) {
case "exit":
done = true;
break ;
case "show":
showWindow();
break;
default:
System.out.println("Unknown command: commands are \"show\" or \"exit\"");
}
}
Platform.exit();
}
}
private void showWindow() throws Exception {
ensureFXApplicationThreadRunning();
Platform.runLater(this::_showWindow);
}
private void _showWindow() {
Stage stage = new Stage();
Button button = new Button("OK");
button.setOnAction(e -> stage.hide());
Scene scene = new Scene(new StackPane(button), 350, 75);
stage.setScene(scene);
stage.show();
stage.toFront();
}
private void ensureFXApplicationThreadRunning() throws Exception {
if (jfxPanel != null) return ;
FutureTask<JFXPanel> fxThreadStarter = new FutureTask<>(() -> {
return new JFXPanel();
});
SwingUtilities.invokeLater(fxThreadStarter);
jfxPanel = fxThreadStarter.get();
}
public static void main(String[] args) throws Exception {
Platform.setImplicitExit(false);
System.out.println("Starting Main....");
new Main().run();
}
}
Here is something more along the lines I would actually follow, if I wanted the user to interact via the OS terminal (i.e. using System.in). This uses the first technique, where the application is driven by an FX Application subclass. Here I create two background threads, one to read commands from System.in, and one to process them, passing them via a BlockingQueue. Even though nothing is displayed in the main FX Application Thread, it is still a very bad idea to block that thread waiting for commands. While the threading adds a small level of complexity, this avoids the "JFXPanel" hack, and doesn't rely on there being an AWT implementation present.
import java.util.Scanner;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class FXDriver extends Application {
BlockingQueue<String> commands ;
ExecutorService exec ;
#Override
public void start(Stage primaryStage) throws Exception {
exec = Executors.newCachedThreadPool(runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t ;
});
commands = new LinkedBlockingQueue<>();
Callable<Void> commandReadThread = () -> {
try (Scanner scanner = new Scanner(System.in)) {
while (true) {
System.out.print("Enter command: ");
commands.put(scanner.nextLine());
}
}
};
Callable<Void> commandProcessingThread = () -> {
while (true) {
processCommand(commands.take());
}
};
Platform.setImplicitExit(false);
exec.submit(commandReadThread);
exec.submit(commandProcessingThread);
}
private void processCommand(String command) {
switch (command.toLowerCase()) {
case "exit":
Platform.exit();
break ;
case "show":
Platform.runLater(this::showWindow);
break;
default:
System.out.println("Unknown command: commands are \"show\" or \"exit\"");
}
}
#Override
public void stop() {
exec.shutdown();
}
private void showWindow() {
Stage stage = new Stage();
Button button = new Button("OK");
button.setOnAction(e -> stage.hide());
Scene scene = new Scene(new StackPane(button), 350, 75);
stage.setScene(scene);
stage.show();
stage.toFront();
}
public static void main(String[] args) {
launch(args);
}
}

update CheckBoxTreeCell styles from outside process

I am building my first javafx (2.2) application. The user selects a number of tasks to execute, by selecting checkboxes in a treeview.
I am trying to figure out how, after a task completes, to change the style of the related TreeCell.
public class WorkbenchSscce extends Application {
public static void main(String...args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
final CheckBoxTreeItem<String> rootNode = new CheckBoxTreeItem<>("parent");
final CheckBoxTreeItem<String> taskOne = new CheckBoxTreeItem<>("task one");
final CheckBoxTreeItem<String> taskTwo = new CheckBoxTreeItem<>("task two");
rootNode.getChildren().addAll(taskOne, taskTwo);
TreeView<String> treeView = new TreeView<>(rootNode);
treeView.setEditable(true);
treeView.setCellFactory(CheckBoxTreeCell.<String>forTreeView());
treeView.setShowRoot(false);
Button executeButton = new Button("Execute");
executeButton.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (taskOne.isSelected()) {
executeTask(1);
/**
* ?????
* give the TreeCell for taskOne a green background, to indicate it is complete
* ?????
*/
}
if (taskTwo.isSelected()) {
executeTask(2);
/**
* ?????
* give the TreeCell for taskTwo a green background, to indicate it is complete
* ?????
*/
}
}
});
VBox box = new VBox();
box.getChildren().addAll(treeView, executeButton);
Scene scene = new Scene(box);
stage.setScene(scene);
stage.show();
}
public void executeTask(int input) {
// do something
}
}
I can see how to style the CheckBoxTreeCells at creation time.
I see how to change styles when user events happen to the TreeView (using EventListeners).
But I can't see how to style a tree cell when the source of the event is internal to the application. See comments in the MouseEvent handler above.
The key is to observe the state of the Task (I used a Service instead of a Task in this example, as it can be run multiple times) from the cell factory. To do this, you need the data type of the TreeItem to be something that has an observable property representing the current state of the task/service. The easiest way to do this, if you can, is to make the data type of the TreeItems the Task itself (so conceptually, your TreeView is displaying Tasks).
This is slightly subtle as the item (i.e. Task) represented by a given cell can change. In this example I just observe the cell's item property, removing a listener that observes the task's state from an item the cell is no longer representing and adding the listener to the item it now represents. If you use the EasyBind framework (and Java 8, which it requires), you can clean this up a bit, doing something like
EasyBind.select(cell.itemProperty())
.selectObject(Service::stateProperty)
.addListener((ov, oldState, newState) -> updateCell(cell) );
Full example (using JavaFX 2.2, though I compiled under Java 8, so some Java 8 features may have snuck in):
import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Service;
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.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.CheckBoxTreeCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.StringConverter;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
final BorderPane root = new BorderPane();
final TreeView<SelectableService> tree = new TreeView<>();
final TreeItem<SelectableService> treeRoot = new TreeItem<>(new SelectableService("Parent"));
for (int i=1; i<=10; i++) {
treeRoot.getChildren().add(new TreeItem<>(new SelectableService("Task "+i)));
}
tree.setRoot(treeRoot);
final Button startButton = new Button("Start selected tasks");
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
for (SelectableService service : findSelectedTasks(treeRoot)) {
service.restart();
}
}
});
final HBox controls = new HBox(5);
controls.getChildren().add(startButton);
controls.setPadding(new Insets(10));
controls.setAlignment(Pos.CENTER);
root.setCenter(tree);
root.setBottom(controls);
tree.setCellFactory(new Callback<TreeView<SelectableService>, TreeCell<SelectableService>>() {
#Override
public TreeCell<SelectableService> call(TreeView<SelectableService> param) {
return createCell();
}
});
Scene scene = new Scene(root, 400, 600);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
private CheckBoxTreeCell<SelectableService> createCell() {
// CheckBoxTreeCell whose check box state is mapped to the selected property of the task:
final CheckBoxTreeCell<SelectableService> cell = new CheckBoxTreeCell<SelectableService>(new Callback<TreeItem<SelectableService>, ObservableValue<Boolean>>() {
#Override
public ObservableValue<Boolean> call(TreeItem<SelectableService> treeItem) {
SelectableService task = treeItem.getValue();
if (task != null) {
return task.selectedProperty();
} else {
return null ;
}
}
});
final ChangeListener<State> taskStateListener = new ChangeListener<State>() {
#Override
public void changed(
ObservableValue<? extends State> observable,
State oldValue, State newValue) {
updateCell(cell);
}
};
cell.itemProperty().addListener(new ChangeListener<SelectableService>() {
#Override
public void changed(
ObservableValue<? extends SelectableService> observable,
SelectableService oldTask, SelectableService newTask) {
if (oldTask != null) {
oldTask.stateProperty().removeListener(taskStateListener);
}
if (newTask != null) {
newTask.stateProperty().addListener(taskStateListener);
}
updateCell(cell);
}
});
cell.setConverter(new StringConverter<TreeItem<SelectableService>>() {
#Override
public String toString(TreeItem<SelectableService> treeItem) {
SelectableService task = treeItem.getValue();
if (task == null) {
return null ;
} else {
return task.getName();
}
}
#Override
public TreeItem<SelectableService> fromString(String string) {
// Not supported
throw new UnsupportedOperationException("Uneditable tree cell does not create SelectableTasks");
}
});
return cell;
}
private void updateCell(CheckBoxTreeCell<SelectableService> cell) {
cell.getStyleClass().removeAll(Arrays.asList("running", "finished", "failed"));
SelectableService task = cell.getItem();
if (task != null) {
State state = task.getState();
// Update style class:
if (state == State.RUNNING) {
cell.getStyleClass().add("running");
} else if (state == State.SUCCEEDED) {
cell.getStyleClass().add("finished");
} else if (state == State.FAILED){
cell.getStyleClass().add("failed");
}
}
}
private Set<SelectableService> findSelectedTasks(TreeItem<SelectableService> treeItem) {
Set<SelectableService> selectedTasks = new HashSet<>();
addTaskAndChildTasksIfSelected(selectedTasks, treeItem) ;
return selectedTasks ;
}
private void addTaskAndChildTasksIfSelected(Set<SelectableService> selectedTasks, TreeItem<SelectableService> treeItem) {
SelectableService task = treeItem.getValue();
if (task != null && task.isSelected()) {
selectedTasks.add(task);
}
for (TreeItem<SelectableService> child : treeItem.getChildren()) {
addTaskAndChildTasksIfSelected(selectedTasks, child);
}
}
public static class SelectableService extends Service<Void> {
private final BooleanProperty selected = new SimpleBooleanProperty(this, "selected", false);
public final BooleanProperty selectedProperty() {
return this.selected;
}
public final boolean isSelected() {
return this.selectedProperty().get();
}
public final void setSelected(final boolean selected) {
this.selectedProperty().set(selected);
}
private final ReadOnlyStringWrapper name = new ReadOnlyStringWrapper(this, "name");
private final void setName(String name) {
this.name.set(name);
}
public final String getName() {
return name.get() ;
}
public final ReadOnlyStringProperty nameProperty() {
return name.getReadOnlyProperty();
}
public SelectableService(String name) {
setExecutor(Executors.newCachedThreadPool(new ThreadFactory() {
#Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t ;
}
}));
setName(name);
}
#Override
public Task<Void> createTask() {
return new Task<Void>() {
#Override
public Void call() throws Exception {
// just a mock task: pauses for a random time, then throws an exception with
// probability 0.25
Random rng = new Random();
Thread.sleep(2000 + rng.nextInt(2000));
if (rng.nextDouble() < 0.25) {
throw new Exception("Task failed");
}
return null ;
}
};
}
}
public static void main(String[] args) {
launch(args);
}
}
application.css is simply
.finished {
-fx-background: green ;
}
.failed {
-fx-background: red ;
}
.running {
-fx-background: yellow ;
}
This is quite considerably cleaner in Java 8, by the way, but since you posted JavaFX 2.2-style code, I assumed you were still using the old version. Java 8 also allows you to use pseudoclasses for the css style, which is a bit nicer (and in general has better performance, though it's a moot point here).

Resources