问题描述
我一直在将我的一个项目迁移到 JavaFX 并开始遇到线程问题.我将附上一个简短的例子.经过大量搜索,我设法解决了问题.我无法在 fx 应用程序线程之外更改 tableView 数据.我将代码从使用 SwingWorker 切换到任务.
I've been migrating a project of mine to JavaFX and started running into thread issues. I'll attach a short example. After much searching I managed to sort out the problem. I can't change the tableView data outside of the fx application thread. I switched my code over from using SwingWorker to a Task.
起初,这一直有效,直到我向表的 observableList 添加了更改侦听器.然后我收到错误不在 FX 应用程序线程上;"
At first, that worked until I added a change listener to the table's observableList. I then received the error "Not on FX application thread;"
当我尝试更新标签的值时,错误发生在 onChanged 方法中.我通过将它包装在 Platform.runLater() 中解决了这个问题.
The error happened inside the onChanged method when I attempted to update a Label's value. I resolved this by wrapping it inside Platform.runLater().
我只是不明白为什么更改标签说它不在应用程序线程上.这是在哪个线程上运行的?另外,我是否通过使用任务将行正确添加到我的表中?在我的实际应用程序中,我可以添加 50k 行,因此为什么要单独的线程以免锁定 UI.
I'm just confused as to why changing the label says it wasn't on the application thread. On what thread was this running? Also, am I adding rows to my table correctly by using a task? In my actual application, I could be adding 50k rows hence why the separate thread so as to not lock up the UI.
public class Temp extends Application{
private ObservableList<String> libraryList = FXCollections.observableArrayList();
public void start(Stage stage) {
Label statusLabel = new Label("stuff goes here");
TableView<String> table = new TableView<String>(libraryList);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
TableColumn<String, String> col = new TableColumn<String, String>("Stuff");
col.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(cellData.getValue()));
table.getColumns().add(col);
libraryList.addListener(new ListChangeListener<String>() {
public void onChanged(Change change) {
// Problem was caused by setting the label's text (prior to adding the runLater)
Platform.runLater(()->{
statusLabel.setText(libraryList.size()+" entries");
});
}
});
// dummy stuff
libraryList.add("foo");
libraryList.add("bar");
Button b = new Button("Press Me");
b.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
FileTask task = new FileTask();
new Thread(task).start();
}
});
BorderPane mainBody = new BorderPane();
mainBody.setTop(statusLabel);
mainBody.setCenter(table);
mainBody.setBottom(b);
Scene scene = new Scene(mainBody);
stage.setScene(scene);
stage.show();
}
class FileTask extends Task<Boolean>{
public FileTask(){
}
protected Boolean call() throws Exception{
Random rand = new Random();
for(int i = 0; i < 5; i++) {
String s = ""+rand.nextInt(Integer.MAX_VALUE);
libraryList.add(s);
}
return true;
}
}
public static void main(String[] args) {
Application.launch(args);
}
}
推荐答案
它按预期工作,您有应用程序线程和任务线程,它们看起来像这样:
It's working as expected, you have the application thread and the task thread, they kind of look like this:
App ------\ ----------------------
Task \-label.setText() Exception
除了 App 线程之外,您不能对任何 UI 进行工作,因此添加 RunLater 可以做到这一点:
You can't do any UI work on anything but the App thread, so adding your RunLater does this:
App ----\ -------------/ RunLater(label.setText()) ----------
Task \-add to list/
效果很好.有几种方法可以根据您的目的进行管理:
which works well. There are a few ways to manage this based on what you want to do:
- 如果您想更新 Task 中的 Table 列表,您可以将 RunLater 调用移动到任务内部,而不是处理程序内部,这样它仍然会让您回到 App 线程.这样,如果您实际上在应用线程上,则无需在处理程序中调用 RunLater.
App ---\ -----------------------/ label.setText() ----------
Task \-RunLater(add to list)/
- 另一种选择是只使用一个 Task> 它将在另一个线程上运行,并返回将要添加的字符串的完整列表.如果您在任务中进行网络调用,获取项目列表,然后在将它们全部下载到表中后添加它们,那么这更有可能是您想要的.
App -----\ ------------------------------/ label.setText() ---/ add to table list-------
Task \-build list, update progress /- return final list /
希望格式保持不变.
这篇关于需要澄清在 javafx 应用程序线程中更改数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!