2

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?

clartaq
  • 5,320
  • 3
  • 39
  • 49
  • 1
    Test the result of `Double.valueOf(s)` before returning it? Or use `BigDecimal`? – Slaw Aug 30 '21 at 22:28
  • 1
    Setting the font is NOT required on macOS. – swpalmer Aug 31 '21 at 01:52
  • same comment as to the other question/answers: don't do any manual pattern matching (it will break with different locales), instead use a NumberFormat. Also: never extend a class just for configuration - use a factory pattern, instead. – kleopatra Aug 31 '21 at 09:45
  • @swpalmer, https://bugs.openjdk.java.net/browse/JDK-8234916 discusses the garbled text font and errors. Fixed in JavaFX 14. I'm restricted to 11. – clartaq Aug 31 '21 at 19:02

1 Answers1

2

Just use the built-in string converter for doubles:

    TextFormatter<Double> tf = new TextFormatter<>(new DoubleStringConverter());
    TextField ntf = new TextField();
    ntf.setTextFormatter(tf);
swpalmer
  • 3,890
  • 2
  • 23
  • 31