I am encountering performance issues when generating large amount of nodes (say 1000) from FXML. If I create the exact node using API, it is much faster (appx 20 times). Is there any way I can increase the performance when generating the nodes from FXML.
Consider the below example. In this example when I press Generate button, I am populating 1000 layout instances from the fxml file. It is approx taking 10sec to generate them. Instead of Fxml, if I create the nodes using API, it is creating them in appx 600ms. For demo purpose I am saying 1000 nodes, in reality it will be much bigger number.
Output when using FXML
Took 9191ms to generate 1000 nodes
Took 11895ms to generate & layout 1000 nodes
Output when using API:
Took 618ms to generate 1000 nodes
Took 3242ms to generate & layout 1000 nodes
Please note that I cannot use any control/layout that uses VirtualFlow. I need to generate all the nodes for other functionality which draws the joining lines between the nodes (like a UML diagram). I have multiple layouts designed using fxml. Unfortunately I cannot switch to API method.
Below is the complete working code:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class FxmlNodesDemo extends Application {
public void start(Stage stage) throws Exception {
Pane scrollContent = new Pane();
Button generate = new Button("Generate");
generate.setOnAction(e -> {
System.out.println("Generating...");
long start = System.currentTimeMillis();
scrollContent.needsLayoutProperty().addListener((obs, old, val) -> {
if (!val) {
System.out.println("Took " + (System.currentTimeMillis() - start) + "ms to generate & layout 1000 nodes");
}
});
generate.setDisable(true);
double y = 0;
for (int i = 0; i < 1000; i++) {
try {
// FXML approach
GridPane pane = FXMLLoader.load(getClass().getResource("nodeLayout.fxml"));
// API approach
// GridPane pane = buildPane();
pane.setLayoutX(i % 2 == 0 ? 0 : 100);
pane.setLayoutY(y);
y = y + 130;
scrollContent.getChildren().add(pane);
} catch (Exception e1) {
e1.printStackTrace();
}
}
System.out.println("Took " + (System.currentTimeMillis() - start) + "ms to generate 1000 nodes");
});
ScrollPane scrollPane = new ScrollPane();
scrollPane.setContent(scrollContent);
scrollPane.setPadding(new Insets(10));
VBox.setVgrow(scrollPane, Priority.ALWAYS);
VBox layout = new VBox(generate, scrollPane);
layout.setSpacing(10);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout, 650, 700);
stage.setScene(scene);
stage.setTitle("Fxml Nodes");
stage.show();
}
/**
* Exact replica of the fxml node.
*/
private GridPane buildPane() {
GridPane gridPane = new GridPane();
gridPane.setAlignment(Pos.CENTER);
gridPane.setStyle("-fx-border-width:1px;-fx-border-color:black;-fx-border-radius:5px;");
gridPane.setPadding(new Insets(5));
Label label = new Label("Number Format Error !");
label.setAlignment(Pos.CENTER);
label.setMaxWidth(Double.MAX_VALUE);
label.setMaxHeight(Double.MAX_VALUE);
label.setStyle("-fx-background-color:red;");
gridPane.add(label, 0, 0, 3, 1);
TextField a = new TextField();
a.setId("a");
gridPane.add(a, 0, 1);
TextField b = new TextField();
b.setId("b");
gridPane.add(b, 1, 1);
TextField c = new TextField();
c.setId("c");
gridPane.add(c, 2, 1);
Button solveButton = new Button("Solve");
solveButton.setMaxWidth(Double.MAX_VALUE);
solveButton.setMaxHeight(Double.MAX_VALUE);
gridPane.add(solveButton, 0, 2, 3, 1);
Button clearButton = new Button("Clear");
clearButton.setMaxWidth(Double.MAX_VALUE);
clearButton.setMaxHeight(Double.MAX_VALUE);
gridPane.add(clearButton, 0, 3, 3, 1);
for (int i = 0; i < 3; i++) {
ColumnConstraints cc = new ColumnConstraints();
cc.setFillWidth(true);
cc.setHgrow(Priority.ALWAYS);
gridPane.getColumnConstraints().add(cc);
}
for (int i = 0; i < 4; i++) {
RowConstraints rc = new RowConstraints();
if (i == 1) {
rc.setVgrow(Priority.NEVER);
} else {
rc.setFillHeight(true);
rc.setVgrow(Priority.ALWAYS);
}
gridPane.getRowConstraints().add(rc);
}
return gridPane;
}
public static void main(String[] args) {
launch(args);
}
}
nodeLayout.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.geometry.Insets?>
<GridPane xmlns:fx="http://javafx.com/fxml" alignment="center" style="-fx-border-width:1px;-fx-border-color:black;-fx-border-radius:5px;">
<Label fx:id="label" text="Number Format Error !" maxWidth="Infinity" maxHeight="Infinity" style="-fx-background-color:red;" alignment="CENTER" GridPane.columnSpan="3"/>
<TextField fx:id="a" GridPane.columnIndex="0" GridPane.rowIndex="1"/>
<TextField fx:id="b" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<TextField fx:id="c" GridPane.columnIndex="2" GridPane.rowIndex="1"/>
<Button fx:id="solveButton" text="Solve" maxWidth="Infinity" maxHeight="Infinity" GridPane.columnSpan="3" GridPane.rowIndex="2" />
<Button fx:id="clearButton" text="Clear" maxWidth="Infinity" maxHeight="Infinity" GridPane.columnSpan="3" GridPane.rowIndex="3" />
<columnConstraints>
<ColumnConstraints fillWidth="true" hgrow="ALWAYS"/>
<ColumnConstraints fillWidth="true" hgrow="ALWAYS"/>
<ColumnConstraints fillWidth="true" hgrow="ALWAYS"/>
</columnConstraints>
<rowConstraints>
<RowConstraints fillHeight="true" vgrow="ALWAYS"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints fillHeight="true" vgrow="ALWAYS"/>
<RowConstraints fillHeight="true" vgrow="ALWAYS"/>
</rowConstraints>
<padding>
<Insets topRightBottomLeft="5"/>
</padding>
</GridPane>