I have a TextField which has been given a TextFormatter, which has a filter that takes the input of the textfield, and ensures it is in a double format. It then uses a DoubleStringConverter to convert the filtered text into a double value. This is in a custom FXML component. See below for minimal example.
public SomeComponent implements Initializable {
@FXML
public TextField inputField;
public int min;
public void initialize(URL location, ResourceBundle resources) {
UnaryOperator<TextFormatter.Change> doubleFilter = change -> {
String newText = change.getControlNewText();
if (newText.matches("-?[0-9]{1,13}(\\.[0-9]*)?")) {
return change;
}
return null;
};
inputField.setTextFormatter(new TextFormatter<>(new DoubleStringConverter(), min, doubleFilter));
}
}
Another FXML component that uses this component ...
public SomeView implements Initializable {
@FXML
SomeComponent comp;
public void initialize(URL location, ResourceBundle resources) {
comp.inputField.getTextFormatter().valueProperty().bindBidirectional(SomeDataManagerClass.SomeSimpleDoubleProperty());
}
}
The bindBidirectional() is attempting to bind the TextField's converted value, to a SimpleDoubleProperty
that exists elsewhere. But this returns the following error:
method Property.bindBidirectional(Property<CAP#1>) is not applicable
(argument mismatch; SimpleDoubleProperty cannot be converted to Property<CAP#1>)
Essentially I want to make use of the TextField's value (not the getText()
, but the converted value) for binding to other properties.
I realise there is something wrong with the captured type inference with the TextFormatter, but I can't see anywhere in the JavaDocs that shows how to make use of the TextFormatter's double value. What is the point in using a DoubleStringConverter to convert the value if there isn't a simple way to extract that double from the TextField? I could use Double.parseDouble(inputField.getText())
but this seems pointless if I have already gone to the trouble of setting up a nice TextFormatter for the field.
How do I make use of the TextFormatter's valueProperty outside of SomeComponent?
The textFormatter
property is an ObjectProperty<TextFormatter<?>>
. Since the TextInputControl
class is not generic there's no way to properly parameterize the textFormatter
property, hence the wildcard. This means whenever you query the property you'll get a TextFormatter<?>
regardless of previously setting it to, for instance, a TextFormatter<Double>
. There's a couple ways you could solve this:
Cast the result to whatever generic type you expect:
var formatter = (TextFormatter<Number>) comp.inputField.getTextFormatter();
formatter.valueProperty().bindBidirectional(SomeDataManagerClass.SomeSimpleDoubleProperty());
Of course, that will lead to an unchecked cast warning. You can suppress the warning if you want via a @SuppressWarnings("unchecked")
annotation but the operation remains unsafe.
Keep your own reference to the TextFormatter<Number>
and provide a method for other code to get it.
public SomeComponent implements Initializable {
@FXML
public TextField inputField;
// maintain generic information
private TextFormatter<Number> inputFieldFormatter;
public int min;
public void initialize(URL location, ResourceBundle resources) {
UnaryOperator<TextFormatter.Change> doubleFilter = change -> {
String newText = change.getControlNewText();
if (newText.matches("-?[0-9]{1,13}(\\.[0-9]*)?")) {
return change;
}
return null;
};
inputFieldFormatter = new TextFormatter<>(new NumberStringConverter(), min, doubleFilter);
inputField.setTextFormatter(inputFieldFormatter));
}
// use this method in your other controller
public TextFormatter<Number> getInputFieldFormatter() {
return inputFieldFormatter;
}
}
Notice in each case I used a TextFormatter<Number>
. This is because a DoubleProperty
is a Property<Number>
and the bindBidirectional
method expects an exact generic match, unlike the bind
method which is upper bounded. This also meant a NumberStringConverter
had to be used.