我正在尝试在执行异步后台ProgressIndicator
项加载时显示ListView
。我想要的行为是:
ListView
项之前,请显示ProgressIndicator
indeterminate进度; ListView
项目; ListView
项目加载后,隐藏ProgressIndicator
。以下是我未成功尝试的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
项未加载。
因此,我怎样才能达到预期的行为?
答案 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();
}
}