我在JavaFX(java版本1.8.0_91)中遇到了一些奇怪的东西。我的理解是,如果想要从单独的线程更新UI,则必须使用Platform.runLater(taskThatUpdates)
或javafx.concurrent
包中的其中一个工具。
但是,如果我有一个TableView
,我可以在其中调用.setItems(someObservableList)
,我可以从单独的帖子更新someObservableList
并查看对TableView
的相应更改,而不是预期Exception in thread "X" java.lang.IllegalStateException: Not on FX application thread; currentThread = X
错误。
如果我将TableView
替换为ListView
,则会出现预期的错误。
情况#1的示例代码:从不同的线程更新TableView
,没有调用Platform.runLater()
- 并且没有错误。
public class Test extends Application {
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage stage) throws Exception {
// Create a table of integers with one column to display
TableView<Integer> data = new TableView<>();
TableColumn<Integer, Integer> num = new TableColumn<>("Number");
num.setCellValueFactory(v -> new ReadOnlyObjectWrapper(v.getValue()));
data.getColumns().add(num);
// Create a window & add the table
VBox root = new VBox();
Scene scene = new Scene(root);
root.getChildren().addAll(data);
stage.setScene(scene);
stage.show();
// Create a list of numbers & bind the table to it
ObservableList<Integer> someNumbers = FXCollections.observableArrayList();
data.setItems(someNumbers);
// Add a new number every second from a different thread
new Thread( () -> {
for (;;) {
try {
Thread.sleep(1000);
someNumbers.add((int) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
情况#2的示例代码:从不同的线程更新ListView
而不调用Platform.runLater()
- 会产生错误。
public class Test extends Application {
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage stage) throws Exception {
// Create a list of integers (instead of a table)
ListView<Integer> data = new ListView<>();
// Create a window & add the table
VBox root = new VBox();
Scene scene = new Scene(root);
root.getChildren().addAll(data);
stage.setScene(scene);
stage.show();
// Create a list of numbers & bind the table to it
ObservableList<Integer> someNumbers = FXCollections.observableArrayList();
data.setItems(someNumbers);
// Add a new number every second from a different thread
new Thread( () -> {
for (;;) {
try {
Thread.sleep(1000);
someNumbers.add((int) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
请注意,唯一的区别是将data
实例化为ListView<Integer>
而不是TableView<Integer>
。
那么这里给出了什么?这是因为在第一个例子中调用了TableColumn::setCellValueFactory()
吗? - 这是我的直觉。我想知道为什么一个不会导致错误而另一个错误,更具体地说是.setItems
调用如何将数据绑定到视图的规则。
答案 0 :(得分:6)
正如@James_D已经提到的,你不应该在任何其他线程上更新属于FX应用程序线程的东西。但是在您的示例中,您已经在另一个线程中更新了ObservableList。无论为什么这对TableView和ListView都不起作用,这是错误的做法。
如果要在支持ObservableLists上执行中间更新,请查看Task类。
返回ObservableList的任务
因为ListView,TableView和其他UI控件和场景图 节点使用ObservableList,通常需要创建和 从Task返回一个ObservableList。当你不在乎显示 中间值,正确编写这样一个任务的最简单方法是 只需在call方法中构造一个ObservableList,然后 在任务结束时返回。
和另一个提示:
返回部分结果的任务
有时您想要创建一个将返回部分结果的任务。 也许你正在构建一个复杂的场景图并想要显示 正在构建的场景图。或许你正在读一篇文章 通过网络的大量数据,并希望显示条目 在数据到达时在TableView中。在这种情况下,有一些 共享状态可用于FX应用程序线程和 背景线程。必须非常小心永远不要更新共享 从FX应用程序线程以外的任何线程状态。
最简单的方法是利用 updateValue(Object)方法。可以从中重复调用此方法 后台线程。更新被合并以防止饱和 FX事件队列。这意味着你可以像你一样经常打电话 喜欢从后台线程,但只有最新的集合 最终确定。
用于对TableView和ListView的ObservableLists进行中间更新的此类Task的示例是:
import java.util.ArrayList;
import java.util.Random;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class IntermediateTest extends Application {
@Override
public void start(Stage primaryStage) {
TableView<Integer> tv = new TableView<>();
TableColumn<Integer, Integer> num = new TableColumn<>("Number");
num.setCellValueFactory(v -> new ReadOnlyObjectWrapper(v.getValue()));
tv.getColumns().add(num);
PartialResultsTask prt = new PartialResultsTask();
tv.setItems(prt.getPartialResults());
ListView<Integer> lv = new ListView<>();
PartialResultsTask prt1 = new PartialResultsTask();
lv.setItems(prt1.getPartialResults());
new Thread(prt).start();
new Thread(prt1).start();
// Create a window & add the table
VBox root = new VBox();
root.getChildren().addAll(tv, lv);
Scene scene = new Scene(root, 300, 450);
primaryStage.setTitle("Data-Adding");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
public class PartialResultsTask extends Task<ObservableList<Integer>> {
private ReadOnlyObjectWrapper<ObservableList<Integer>> partialResults
= new ReadOnlyObjectWrapper<>(this, "partialResults",
FXCollections.observableArrayList(new ArrayList()));
public final ObservableList getPartialResults() {
return partialResults.get();
}
public final ReadOnlyObjectProperty<ObservableList<Integer>> partialResultsProperty() {
return partialResults.getReadOnlyProperty();
}
@Override
protected ObservableList call() throws Exception {
updateMessage("Creating Integers...");
Random rnd = new Random();
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
if (isCancelled()) {
break;
}
final Integer r = rnd.ints(100, 10000).findFirst().getAsInt();
Platform.runLater(() -> {
getPartialResults().add(r);
});
updateProgress(i, 10);
}
return partialResults.get();
}
}
}