首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在异步加载ProgressIndicator项时显示ListView

问在异步加载ProgressIndicator项时显示ListView
EN

Stack Overflow用户
提问于 2013-09-20 18:23:56
回答 2查看 11.4K关注 0票数 4

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

  1. 在开始加载ListView项之前,显示一个带有不确定进度的ProgressIndicator;
  2. 异步开始加载ListView项;
  3. 完成ListView项的加载后,隐藏ProgressIndicator。

以下是我失败尝试的一个ssce:

代码语言:javascript
复制
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项不会被加载。

因此,我怎样才能达到理想的行为?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2013-09-20 19:56:18

问题是,在本例中,我误用了Platform.runLater(...)方法,因此误用了JavaFX应用程序线程。

正如在方法文档中提到的,这个方法

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

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

因此,当我开始从这个线程加载ListView项时,UI会变得没有响应(这也是声明的这里),直到加载完成为止。

为了解决这个问题,必须在另一个线程上加载ListView项,只有ListView更新必须在Application执行。

上述更正如下:

代码语言:javascript
复制
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();
    }
}
票数 5
EN

Stack Overflow用户

发布于 2013-09-20 20:30:01

Crferreira的自我回答是非常好的。

这个答案只是演示了一个替代实现,它不需要使用任何Platform.runLater调用,而是使用JavaFX 任务 (以及Java 8 lambda语法)。

代码语言:javascript
复制
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); }
}
票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/18923413

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档