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:
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.