需要澄清在Javafx应用程序线程中更改数据

时间:2019-04-17 12:43:43

标签: java multithreading javafx

我一直在将我的项目迁移到JavaFX,并开始遇到线程问题。我将举一个简短的例子。经过大量搜索后,我设法解决了问题。我无法在fx应用程序线程之外更改tableView数据。我将代码从使用SwingWorker切换到了任务。

起初,这一直有效,直到我向表的observableList添加了一个更改侦听器。然后,我收到错误“不在FX应用程序线程上;”

当我尝试更新Label的值时,该错误发生在onChanged方法内部。我通过将其包装在Platform.runLater()中来解决此问题。

我只是对为什么更改标签说它不在应用程序线程上感到困惑。在哪个线程上运行?另外,我是否通过使用任务将行正确添加到表中?在我的实际应用程序中,我可能要添加5万行,因此为什么要使用单独的线程以免锁定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);

    }
}

2 个答案:

答案 0 :(得分:1)

它按预期工作,您拥有应用程序线程和任务线程,它们看起来像这样:

App ------\ ----------------------
Task       \-label.setText() Exception

除了App线程外,您无法对任何UI进行任何操作,因此添加RunLater可以做到这一点:

App ----\ -------------/ RunLater(label.setText()) ----------
Task     \-add to list/

效果很好。有几种方法可以根据您要执行的操作进行管理:

  • 如果要更新“任务”中的“表”列表,可以将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 /

希望格式保持不变。

答案 1 :(得分:1)

考虑将视图所需的信息封装在一个单独的类(通常称为模型)中。 视图应通过侦听器或绑定对模型中的更改做出响应。
您可以使用一个或多个线程来更新模型:

import java.util.Random;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Temp extends Application{

    @Override
    public void start(Stage stage) {

        Model model = new Model();

        Label statusLabel = new Label("stuff goes here");

        TableView<String> table = new TableView<>(model.getLibraryList());
        table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);

        TableColumn<String, String> col = new TableColumn<>("Stuff");
        col.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(cellData.getValue()));
        table.getColumns().add(col);
        statusLabel.textProperty().bind(Bindings.concat(model.sizeProperty.asString(), " entries"));

        // dummy stuff
        model.add("foo");  model.add("bar");

        Button b = new Button("Press Me");
        b.setOnAction(e -> {
            FileTask task = new FileTask(model);
            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 Model {

        private final ObservableList<String> libraryList;
        private final IntegerProperty sizeProperty;

        Model(){
            libraryList = FXCollections.observableArrayList();
            sizeProperty = new SimpleIntegerProperty(0);
            libraryList.addListener((ListChangeListener<String>) change -> {
                Platform.runLater(()->sizeProperty.set(libraryList.size()));
            });
        }

        //synchronize if you want to use multithread
        void add(String string) {
            Platform.runLater(()->sizeProperty.set(libraryList.add(string)));
        }

        ObservableList<String> getLibraryList() {
            return libraryList;
        }

        IntegerProperty getSizeProperty() {
            return sizeProperty;
        }
    }

    class FileTask implements Runnable{

        private final  Model model;

        public FileTask(Model model){
            this.model = model;
        }

        @Override
        public void run() {
            Random rand = new Random();
            for(int i = 0; i < 5; i++) {
                String s = ""+rand.nextInt(Integer.MAX_VALUE);
                model.add(s);
            }
        }
    }

    public static void main(String[] args) {
        Application.launch(args);
    }
}