Issue
I have a JavaFX TextField
specialized to accept numbers, including scientific notation. It does pretty much everything I want. But, because it accepts scientific notation, it is easy for a user to enter a number beyond the range that can be represented by a double
. When they do, the TextField
displays "Infinity" (or "-Infinity"). When that happens the field can no longer be edited to correct the problem. The contents cannot be selected and deleted either. Tapping the "Escape" key does not return to the previous contents.
Here is an SSCCE, based closely on the answer by James_D to this question a few years ago.
import java.text.DecimalFormatSymbols;
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class NumericTextFieldDemo extends Application {
char sep = new DecimalFormatSymbols().getDecimalSeparator();
String negStarter = new StringBuilder("-").append(sep).toString();
String posStarter = new StringBuilder("+").append(sep).toString();
String patternStr = new StringBuilder()
.append("[+|-]?(([1-9][0-9]*)|0)?(\\")
.append(sep)
.append("[0-9]*)?(([e|E][+|-]?[0-9]*)?)")
.toString();
Pattern validEditingState = Pattern.compile(patternStr);
class NumericTextField extends TextField {
UnaryOperator<TextFormatter.Change> filter = c -> {
String text = c.getControlNewText();
if (validEditingState.matcher(text).matches()) {
return c;
} else {
return null;
}
};
StringConverter<Double> converter = new StringConverter<Double>() {
@Override
public Double fromString(String s) {
if (s.isEmpty() || "-".equals(s) || "+".equals(s)
|| negStarter.equals(s) || posStarter.equals(s)) {
return 0.0;
} else {
return Double.valueOf(s);
}
}
@Override
public String toString(Double d) {
return d.toString();
}
};
NumericTextField(double initValue) {
TextFormatter<Double> textFormatter = new TextFormatter<>(converter, initValue, filter);
textFormatter.valueProperty().addListener((ObservableValue<? extends Double> obs, Double oldValue, Double newValue) -> {
System.out.println("User entered value: " + newValue);
});
setTextFormatter(textFormatter);
}
NumericTextField() {
this(0.0);
}
}
@Override
public void start(Stage primaryStage) throws Exception {
NumericTextField ntf = new NumericTextField();
// Setting the font seems to be required on macOS.
ntf.setFont(new Font("Arial", 14));
VBox root = new VBox(5, ntf);
root.setAlignment(Pos.CENTER);
primaryStage.setScene(new Scene(root, 250, 150));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Is there any way to catch the infinities and leave the TextField
in a usable state? Is there some change that could be made to the class to prevent entering such numbers in the first place?
Solution
Just use the built-in string converter for doubles:
TextFormatter<Double> tf = new TextFormatter<>(new DoubleStringConverter());
TextField ntf = new TextField();
ntf.setTextFormatter(tf);
Answered By - swpalmer
Answer Checked By - David Goodson (JavaFixing Volunteer)