Display a ProgressIndicator during an async loading of ListView items

只谈情不闲聊 提交于 2019-12-31 17:25:20

问题


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?


回答1:


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); }
}



回答2:


The problem is that, at the presented example, I am misusing the Platform.runLater(...) method, and consequently, the JavaFX Application Thread.

As mentioned at the Platform.runLater() method documentation, this method

Run the specified Runnable on the JavaFX Application Thread at some unspecified time in the future.

And, the JavaFX Application Thread is the thread from which the JavaFX scene graph can be accessed and modified by the developer code, visually reflecting the performed modifications.

Thus, when I start loading the ListView items from this thread, the UI becomes unresponsive (this is also stated here) until the loading is finished.

To solve the problem, the ListView items must be loaded at another thread and only the ListView update must be performed at Application Thread.

The above correction is presented in the following:

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) {
                final List<String> loadedItems = new LinkedList<String>();

                // 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
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(2000l); // just emulates some loading time

                            // populates the list view with dummy items
                            while (loadedItems.size() < 10) loadedItems.add("Item " + loadedItems.size());
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } finally {
                            // just updates the list view items at the
                            // Application Thread
                            Platform.runLater(new Runnable() {
                                @Override
                                public void run() {
                                    listItems.addAll(loadedItems);
                                    loadingIndicator.setVisible(false); // stop displaying the loading indicator
                                }
                            });
                        }
                    }
                }).start();
            }
        });

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

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


来源:https://stackoverflow.com/questions/18923413/display-a-progressindicator-during-an-async-loading-of-listview-items

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!