In JavaFX in my controller after clicking on Play button starting method 'createTTV()' creating nested TreeTableView(This treetableview will be even larger, but I don't want to paste too much unnecessary code). The method has many UI/not UI elements which are impossible to split for UI/not UI. Is there a way for running such method in background and when everything ready updating UI with not locking whole UI? My code below works, but the UI obviously doesn't respond until the createTTV() method finishes running.
public void buildUi() {
vBoxForProgressIndicator.setVisible(false);
fuses_ttv.setRoot(root_FRCs);
btnPlay.setOnAction(event ->{
clear_hook();
vBoxForTableView.setVisible(false);
vBoxForProgressIndicator.setVisible(true);
new Thread(()->{
createTTV();
Platform.runLater(new Runnable() {
@Override
public void run() {
vBoxForProgressIndicator.setVisible(false);
vBoxForTableView.setVisible(true);
}
});
}).start();
});
}
private void createTTV(){
tdoStructure = model.getDataStructure();
allFRCs = tdoStructure.getAllFRCsAndCandidates();
allWires = tdoStructure.getAllWires();
allParts = tdoStructure.collectAllParts();
allFusesAndRelays = tdoStructure.getAllFusesAndRelais();
Platform.runLater(()->{
root_FRCs.getChildren().clear();
partDescription_ttc.setCellValueFactory(new TreeItemPropertyValueFactory<>("description"));
partIdentification_ttc.setCellValueFactory(new TreeItemPropertyValueFactory<>("identification"));
partNo_ttc.setCellValueFactory(new TreeItemPropertyValueFactory<>("partNumber"));
slot_ttc.setCellValueFactory(new TreeItemPropertyValueFactory<>("slot"));
pin_ttc.setCellValueFactory(new TreeItemPropertyValueFactory<>("pin"));
moduleFamilyName_ttc.setCellValueFactory(new TreeItemPropertyValueFactory<>("module"));
partType_ttc.setCellValueFactory(new TreeItemPropertyValueFactory<>("type"));
variants_ttc.setCellValueFactory(new TreeItemPropertyValueFactory<>("variants"));
if (frcElement != null) {
TreeItem frcTreeItem = new TreeItem<>(new FuseValidationTreeObject(frcElement));
if (!frcElement.getFrcSlots().isEmpty()) {
for (Map.Entry<String, ArrayList<String>> entrySlot : frcElement.getFrcSlots().entrySet()) {
String slot = entrySlot.getKey();
TreeItem slotItem = new TreeItem<>(new FuseValidationTreeObject(slot));
slotItem.setExpanded(true);
frcTreeItem.getChildren().add(slotItem);
for (Part fuseOrRelay : allFusesAndRelays) {
LinkedList<String> fuseOrRelayVariants = AppUtils.getAllVariantsForPart(fuseOrRelay);
if (fuseOrRelay.getReferencePartShortName().equals(frcElement.getShortName()) && AppUtils.getSlotIdFromFuseOrRelayName(fuseOrRelay.getShortName()).equals(slot)) {
TreeItem fuseRelayItem = new TreeItem(new FuseValidationTreeObject(fuseOrRelay));
boolean[] fuseOrRelayHasNoWires = {true};
//assign wires to fuse or relay
allWires.forEach(wire -> {
LinkedList<String> wireVariants = AppUtils.getAllVariantsForPart(wire);
if (fuseOrRelay.getReferencePartShortName().equals(wire.getWireToShortName())
&& entrySlot.getValue().contains(wire.getWireToPin()) && fuseOrRelay.getMemberModuleFamily().getDescription().equals(wire.getMemberModuleFamily().getDescription())) {
if (AppUtils.containsWireVariantsAnyOfFuseVariant(wireVariants, fuseOrRelayVariants)) {
TreeItem wireToItem = new TreeItem<>(new FuseValidationTreeObject(wire));
wireToItem.setExpanded(true);
fuseRelayItem.getChildren().add(wireToItem);
fuseOrRelayHasNoWires[0] = false;
if (!fuseOrRelayVariants.containsAll(wireVariants)) {
if (!wiresWithWrongVariants.contains(wire.getWireNumber())) {
wiresWithWrongVariants.add(wire.getWireNumber());
}
}
}
}
if (fuseOrRelay.getReferencePartShortName().equals(wire.getWireFromShortName())
&& entrySlot.getValue().contains(wire.getWireFromPin()) && fuseOrRelay.getMemberModuleFamily().getDescription().equals(wire.getMemberModuleFamily().getDescription())) {
if (AppUtils.containsWireVariantsAnyOfFuseVariant(wireVariants, fuseOrRelayVariants)) {
TreeItem wireFromItem = new TreeItem<>(new FuseValidationTreeObject(wire));
wireFromItem.setExpanded(true);
fuseRelayItem.getChildren().add(wireFromItem);
fuseOrRelayHasNoWires[0] = false;
if (!fuseOrRelayVariants.containsAll(wireVariants)) {
if (!wiresWithWrongVariants.contains(wire.getWireNumber())) {
wiresWithWrongVariants.add(wire.getWireNumber());
}
}
}
}
});
fuseRelayItem.setExpanded(true);
if (fuseOrRelayHasNoWires[0]) {
emptyFusesOrRelays.add(fuseOrRelay.getShortName());
}
slotItem.getChildren().add(fuseRelayItem);
}
}
if (slotItem.isLeaf()) {
//insert empty slot into emptySlots Map
if (emptySlots.containsKey(frcElement.getShortName())) {
emptySlots.get(frcElement.getShortName()).add(0, slot);
} else {
emptySlots.put(frcElement.getShortName(), new ArrayList<>(Arrays.asList(slot)));
}
//some more conditions
allWires.forEach(wire -> {
//if there is no fuse in the slot but there is still wire to fuse in this slot
if (wire.getWireToShortName().equals(frcElement.getShortName()) && entrySlot.getValue().contains(wire.getWireToPin())) {
wiresWithNoFuseOrRelay.add(wire);
TreeItem emptyWireTo = new TreeItem<>(new FuseValidationTreeObject(wire));
slotItem.getChildren().add(emptyWireTo);
}
if (wire.getWireFromShortName().equals(frcElement.getShortName()) && entrySlot.getValue().contains(wire.getWireFromPin())) {
wiresWithNoFuseOrRelay.add(wire);
TreeItem emptyWireFrom = new TreeItem<>(new FuseValidationTreeObject(wire));
slotItem.getChildren().add(emptyWireFrom);
}
});
}
}
}
frcTreeItem.setExpanded(true);
root_FRCs.getChildren().add(frcTreeItem);
}
}
}
UI elements can be created on another thread if they aren't attached to an active scene.
For most JavaFX controls (there are a few exceptions such as WebView
), the rule to create or modify them only on the JavaFX thread only applies to modifications to a live scene graph that is attached to a Stage
which is showing, or for which a snapshot
is being made. The controls which can only be created on the JavaFX thread are listed as such in their documentation.
For example, it is perfectly fine to load an FXML file asynchronously on another thread (e.g. in a Task) and attach it to the scene graph on the JavaFX application thread later.
You can use runLater
to add a created TreeTableView
to an active scene.
Quoting from the Task
documentation, section: "A Task Which Modifies the Scene Graph".
Generally, Tasks should not interact directly with the UI. Doing so creates a tight coupling between a specific Task implementation and a specific part of your UI. However, when you do want to create such a coupling, you must ensure that you use
Platform.runLater
so that any modifications of the scene graph occur on the FX Application Thread.
In the example provided, UI elements are created on the task thread, and Platform.runLater()
is only used to add the created element to the scene graph.
final Group group = new Group();
Task<Void> task = new Task<Void>() {
@Override protected Void call() throws Exception {
for (int i=0; i<100; i++) {
if (isCancelled()) break;
final Rectangle r = new Rectangle(10, 10);
r.setX(10 * i);
Platform.runLater(new Runnable() {
@Override public void run() {
group.getChildren().add(r);
}
});
}
return null;
}
};
Alternately, you can create the TreeTableView
in a task and add it to the scene onSucceeded
So you could write this:
Task<TreeTableView> task = new Task<TreeTableView>() {
@Override protected TreeTableView call() {
return createTreeTableView();
}
private TreeTableView createTreeTableView() {
// Create and return a new TreeTableView with all new TreeItems initialized.
}
};
task.setOnSucceeded(e ->
myParentNode.getChildren().add(
task.getValue()
)
);
Or, you can create only the TreeItems
in your Task
If you are still uncomfortable with creating UI elements in another thread, then you can follow James_D's advice from comments:
The
TreeItem
instances are not UI components; they only contain data. You can create all theTreeItem
instances on the background thread. Then just create the tree table, columns, configure them, and set the root of the tree table on the FX Application Thread. The latter operations are not excessively time-consuming.