简体   繁体   中英

How to properly handle a TextField binding and keep the nodereference separate from the Task

My question applies to a broader range of nodes than just TextField, but my example includes only this specific type of node.

My application changes the content of a TextField which displays the name of a country based on the selection of a city made from a ChoiceBox:

我的应用程序更改了TextField的内容,该文本字段基于ChoiceBox做出的城市选择来显示国家名称

I accomplish this by setting the text of countryTextField in a Task<Void> using the onAction event handler for the choice box:

@FXML
public void handleCityChoice() {
    new Thread(new CityChoiceTask(cityChoiceBox.getValue())).start();
}

class CityChoiceTask extends Task<Void> {
    String selection;
    CityChoiceTask(String selection) {
        this.selection = selection;
    }
    @Override
    public Void call() {
        Platform.runLater(() -> countryTextField.setText(Datasource.getInstance().
                getCountryNameByCityName(selection)));
        return null;
    }
}

It works just fine, but really what I want to do is completely separate the node referencing from the Task code because it is a best practice to do so (I'm just a student, so correct me if I'm wrong about this, but it's what I was taught). I tried the following, but keep throwing a NullPointerException , I believe it is because the task thread doesn't complete before the bind is executed:

@FXML
public void handleCityChoice() {
    Task task = new CityChoiceTask(cityChoiceBox.getValue());
    new Thread(task).start();
    countryTextField.textProperty().bind(task.valueProperty());
}

class CityChoiceTask extends Task<SimpleStringProperty> {
    String selection;
    CityChoiceTask(String selection) {
        this.selection = selection;
    }
    @Override
    public SimpleStringProperty call() {
        String result = Datasource.getInstance().getCountryNameByCityName(selection));
        return new SimpleStringProperty(result);
    }
}

Also, System.out.println(task.valueProperty());

prints:

ObjectProperty [bean: controller.AddCustomer$CityChoiceTask@8b85d08, name: value, value: null]

Is the thread not executing in time indeed why it's throwing a NullPointerException , and is there a way to fix it?

On a side note, this sort of bind works just fine when using TableView. For instance, using a Task that returns a ValueProperty of FXCollections.observableArrayList , something like myTableView.itemsProperty().bind(task); works just fine, regardless of when the thread finished execution.

You need to use the proper return type as type parameter to the Task . Since you want to assign a Property, you need to use String . 属性,因此需要使用String(Your compiler would have complained about this error, if you didn't use the raw type.)

@Override
public void start(Stage primaryStage) {
    TextField text = new TextField();

    Button btn = new Button("Run");
    btn.setOnAction((ActionEvent event) -> {
        Task<String> task = new Task<String>() {

            @Override
            protected String call() throws Exception {
                Thread.sleep(1000);
                return "Hello World";
            }

        };
        text.textProperty().bind(task.valueProperty());
        new Thread(task).start();
    });

    VBox root = new VBox(btn, text);

    Scene scene = new Scene(root);

    primaryStage.setScene(scene);
    primaryStage.show();
}

You could also post intermediate updates using updateValue method.


Also note that your first snippet does not really do any work on the non-application thread. The task is only used to schedule the code for retrieving the information later on the application thread. Also using Task for this purpose is unnecessary. A simple Runnable would suffice:

final String selection = cityChoiceBox.getValue();
new Thread(() ->  {
    // do this on this thread, not on the JavaFX application thread
    final String result = Datasource.getInstance().getCountryNameByCityName(selection);

    Platform.runLater(() -> countryTextField.setText(result));
}).start();

You should change type of your task to String and call updateValue() from the call() method of your task:

class CityChoiceTask extends Task<String> {

    private final String selection;

    CityChoiceTask(String selection) {
        this.selection = selection;
    }

    @Override
    public String call() {
        String result = Datasource.getInstance().getCountryNameByCityName(selection);
        updateValue(result);
        return result;
    }
}

And in your handleCityChoice() method you should bind countryTextField.textProperty() prior to starting thread as it can finish execution before the next line of code (creating the binding) will be executed, making your countryText field be never updated:

@FXML
public void handleCityChoice() {
    CityChoiceTask task = new CityChoiceTask(cityChoiceBox.getValue());
    countryTextField.textProperty().bind(task.valueProperty());
    new Thread(task).start();

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM