Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Display a ProgressIndicator during an async loading of ListView items

Tags:

javafx-2

I am trying to display a ProgressIndicator while performing an async background ListView item loading. The behaviour that I desire is:

  1. Before start loading the ListView items, display a ProgressIndicator with a indeterminate progress;
  2. Asynchronously start loading the ListView items;
  3. After the ListView items loading was finished, hide the ProgressIndicator.

Here is a ssce of my unsuccessful attempt:

public class AsyncLoadingExample extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        final ListView<String> listView = new ListView<String>();
        final ObservableList<String> listItems = FXCollections.observableArrayList();
        final ProgressIndicator loadingIndicator = new ProgressIndicator();
        final Button button = new Button("Click me to start loading");

        primaryStage.setTitle("Async Loading Example");        

        listView.setPrefSize(200, 250);
        listView.setItems(listItems);

        loadingIndicator.setVisible(false);

        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                // I have hoped it whould start displaying the loading indicator (actually, at the end of this
                // method execution (EventHandler.handle(ActionEvent))
                loadingIndicator.setVisible(true); 

                // asynchronously loads the list view items
                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(2000l); // just emulates some loading time

                            // populates the list view with dummy items
                            while (listItems.size() < 10) listItems.add("Item " + listItems.size());
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } finally { 
                            loadingIndicator.setVisible(false);  // stop displaying the loading indicator
                        }                   
                    }
                });
            }
        });

        VBox root = VBoxBuilder.create()
            .children(
                StackPaneBuilder.create().children(listView, loadingIndicator).build(), 
                button                  
            )
            .build();

        primaryStage.setScene(new Scene(root, 200, 250));
        primaryStage.show();
    }
}

In this example, the ListView items are loaded asynchronously. However, the ProgressIndicator do not show up. Still in this example, if I omit all the Platform.runLater(...) code, the ProgressIndicator shows up, but, of course, the ListView items are not loaded.

Thus, how can I achieve the desired behaviour?

like image 979
Crferreira Avatar asked Dec 20 '22 00:12

Crferreira


1 Answers

Crferreira's self answer is perfectly fine.

This answer just demonstrates an alternate implementation that does not require the use of any Platform.runLater calls and instead uses a JavaFX Task (as well as Java 8 lambda syntax).

import javafx.application.Application;
import javafx.collections.*;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.util.*;

public class AsyncLoadingExample extends Application {
    private void loadItems(final ObservableList<String> listItems, final ProgressIndicator loadingIndicator) {
        if (loadingIndicator.isVisible()) {
            return;
        }

        // clears the list items and start displaying the loading indicator at the Application Thread
        listItems.clear();
        loadingIndicator.setVisible(true);

        // loads the items at another thread, asynchronously
        Task listLoader = new Task<List<String>>() {
            {
                setOnSucceeded(workerStateEvent -> {
                    listItems.setAll(getValue());
                    loadingIndicator.setVisible(false); // stop displaying the loading indicator
                });

                setOnFailed(workerStateEvent -> getException().printStackTrace());
            }

            @Override
            protected List<String> call() throws Exception {
                final List<String> loadedItems = new LinkedList<>();

                Thread.sleep(2000l); // just emulates some loading time

                // populates the list view with dummy items
                while (loadedItems.size() < 10) {
                    loadedItems.add("Item " + loadedItems.size());
                }

                return loadedItems;
            }
        };

        Thread loadingThread = new Thread(listLoader, "list-loader");
        loadingThread.setDaemon(true);
        loadingThread.start();
    }

    @Override
    public void start(Stage primaryStage) {
        final ListView<String> listView = new ListView<>();
        final ObservableList<String> listItems = FXCollections.observableArrayList();
        final ProgressIndicator loadingIndicator = new ProgressIndicator();
        final Button button = new Button("Click me to start loading");

        primaryStage.setTitle("Async Loading Example");        

        listView.setPrefSize(200, 250);
        listView.setItems(listItems);

        loadingIndicator.setVisible(false);

        button.setOnAction(event -> loadItems(listItems, loadingIndicator));

        VBox root = new VBox(
                new StackPane(
                        listView,
                        loadingIndicator
                ),
                button
        );

        primaryStage.setScene(new Scene(root, 200, 250));
        primaryStage.show();
    }

    public static void main(String[] args) { launch(args); }
}
like image 155
jewelsea Avatar answered Dec 28 '22 09:12

jewelsea