在ListView项的异步加载期间显示ProgressIndicator

时间:2013-09-20 18:23:56

标签: javafx-2

我正在尝试在执行异步后台ProgressIndicator项加载时显示ListView。我想要的行为是:

  1. 在开始加载ListView项之前,请显示ProgressIndicator indeterminate进度;
  2. 异步开始加载ListView项目;
  3. 完成ListView项目加载后,隐藏ProgressIndicator
  4. 以下是我未成功尝试的ssce

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

    在此示例中,ListView项以异步方式加载。但是,ProgressIndicator不显示。仍然在此示例中,如果我省略所有Platform.runLater(...)代码,ProgressIndicator会显示,但是,当然,ListView项未加载。

    因此,我怎样才能达到预期的行为?

2 个答案:

答案 0 :(得分:5)

Crferreira的自我回答非常好。

此答案仅演示了一种替代实现,它不需要使用任何Platform.runLater调用,而是使用JavaFX Task(以及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); }
}

答案 1 :(得分:4)

问题在于,在所提供的示例中,我滥用了Platform.runLater(...)方法,因此滥用了JavaFX应用程序线程。

Platform.runLater() method documentation所述,此方法

  

将来某个未指定的时间在JavaFX应用程序线程上运行指定的Runnable。

而且,JavaFX应用程序线程是开发人员代码可以从中访问和修改JavaFX场景图的线程,可以直观地反映执行的修改。

因此,当我开始从此线程加载ListView项时,UI变得无响应(这也称为here),直到加载完成。

要解决此问题,必须在另一个线程上加载ListView项,并且只能在应用程序线程上执行ListView更新。

以上修正如下:

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