UI Freezes Even with Task & Platform.runLater (Full Example)

Viewed 2156

I've read similar questions but my UI is still freezing when I add many nodes to a VBox. I've provided a fully functional program below which demonstrates the problem clearly.

After 4 seconds, the ProgressIndicator freezes as 5000 nodes are added to the VBox. This is an excessive amount used to demonstrate the JavaFX thread freezing despite using Task (for non-UI work) and then Platform.runLater() for adding the nodes to the scene.

In my actual application, instead of adding blank TitlePanes I'm adding a TitlePane obtained from an FXML file via new FXMLLoader(), and the resulting loader.load() then initializes the associated controller, which in turn initializes some moderately demanding computations - which are being performed on the JavaFX thread! So even though I'm adding closer to 250 nodes, the UI still freezes when the Platform.runLater is eventually used. How do I keep the ProgressIndicator from freezing until the red background is shown?

Full Example:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Accordion;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.Timer;
import java.util.TimerTask;

public class FreezingUI extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

        VBox mainBox = new VBox();
        mainBox.setPrefHeight(800);
        mainBox.setStyle("-fx-background-color: #f1f1f1; -fx-alignment: center");
        Label label = new Label();
        label.setMinHeight(50);
        label.setStyle("-fx-font-size: 24px; -fx-text-fill: #515151");

        ProgressIndicator progressIndicator = new ProgressIndicator(ProgressIndicator.INDETERMINATE_PROGRESS);
        mainBox.getChildren().addAll(progressIndicator, label);

        Scene scene = new Scene(mainBox, 500, 800);
        primaryStage.setScene(scene);
        primaryStage.show();

        Timer timer = new Timer();
        TimerTask task = new TimerTask(){
            private int i = 4;
            public void run(){
                if (i >= 0) {
                    Platform.runLater(()->{
                        label.setText("Freezing in " + i--);
                    });
                }else{
                    addNodesToUI(mainBox);
                    timer.cancel();
                }
            }
        };
        timer.scheduleAtFixedRate(task, 0, 1000);
    }

    private void addNodesToUI(VBox mainBox) {
        final int[] i = {0};
        Platform.runLater(() -> {
            Accordion temp = new Accordion();
            mainBox.getChildren().add(temp);
            while (i[0] < 5000) {

                TitledPane tp = new TitledPane();
                tp.setPrefWidth(300);
                tp.setPrefHeight(12);
                tp.setPadding(new Insets(10));
                tp.setStyle("-fx-background-color: red;");
                temp.getPanes().add(tp);
                i[0]++;

            }

        });
    }
}
1 Answers

This is happening because you are asking the UI thread to do a big bunch of things in one big lump. There is no way for the UI thread to exit the while loop until all 5000 nodes are created and added to the scene.

private void addNodesToUI(VBox mainBox) {
    final int[] i = {0};

    Accordion temp = new Accordion();

    Platform.runLater(() -> {
        mainBox.getChildren().add(temp);
    });

    while (i[0] < 5000) {
        TitledPane tp = new TitledPane();
        tp.setPrefWidth(300);
        tp.setPrefHeight(12);
        tp.setPadding(new Insets(10));
        tp.setStyle("-fx-background-color: red;");

        i[0]++;

        Platform.runLater(() -> {
            temp.getPanes().add(tp);
        });
    }
}

This will allow your nodes to be created in small batches. This way, the UI thread can attempt to render the UI while the nodes are added progressively.

For your FXML case, you can create and load the FXML in another thread. You only need to be in UI thread when you attach a scene branch into the scene. However, I would suspect that would only mitigate the effects, as you are still going to attach a big chunk at one go.

Related