How to implement a NumberField in javaFX 2.0?

Anuruddha picture Anuruddha · Dec 5, 2011 · Viewed 36.5k times · Source

I need to insert a number field into my UI. So I need to check the key events on a text field in order to check whether the input character is a number. I created a class by extending TextField. If there is a method in TextField class which handles keyEvents, i can simply overide that method with suits to number field. Any ideas?

Thanks

Answer

jewelsea picture jewelsea · May 15, 2013

Update 27 May 2016

Java 8u40 introduced the TextFormatter class which is the recommended way to accomplish this functionality (though the solution provided in this answer will still work). For further information see Uwe's answer, Hassan's answer and other answers mentioning TextFormatter to the following question:

There is also this solution from another answer to this question which I haven't tried, but looks good and a StackOverflow moderator deleted:

TextField numberField = new TextField();
numberField.setTextFormatter(new TextFormatter<>(new NumberStringConverter()));

The code above misses the UnaryOperator filter for the TextFormatter, which you usually also require (otherwise, the field won't display restrict user input to only the formatted value, it will just allow you to monitor the unformatted value via the text formatters value property). To extend the solution to use a filter, code like that below could be used:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.stage.Stage;
import javafx.util.converter.NumberStringConverter;

import java.text.ParsePosition;
import java.util.function.UnaryOperator;

public class NumberConverterFieldTest extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) {
        TextField numberField = new TextField();
        NumberStringFilteredConverter converter = new NumberStringFilteredConverter();
        final TextFormatter<Number> formatter = new TextFormatter<>(
                converter,
                0,
                converter.getFilter()
        );

        numberField.setTextFormatter(formatter);

        formatter.valueProperty().addListener((observable, oldValue, newValue) ->
                System.out.println(newValue)
        );

        stage.setScene(new Scene(numberField));
        stage.show();
    }

    class NumberStringFilteredConverter extends NumberStringConverter {
        // Note, if needed you can add in appropriate constructors 
        // here to set locale, pattern matching or an explicit
        // type of NumberFormat.
        // 
        // For more information on format control, see 
        //    the NumberStringConverter constructors
        //    DecimalFormat class 
        //    NumberFormat static methods for examples.
        // This solution can instead extend other NumberStringConverters if needed
        //    e.g. CurrencyStringConverter or PercentageStringConverter.

        public UnaryOperator<TextFormatter.Change> getFilter() {
            return change -> {
                String newText = change.getControlNewText();
                if (newText.isEmpty()) {
                    return change;
                }

                ParsePosition parsePosition = new ParsePosition( 0 );
                Object object = getNumberFormat().parse( newText, parsePosition );
                if ( object == null || parsePosition.getIndex() < newText.length()) {
                    return null;
                } else {
                    return change;
                }
            };
        }
    }
}

When running the above example, edit the input field and press the enter key to see the value updated (updated value is output to System.out when changed).

For a tutorial see:


This is the same solution which Urs references, however I just placed it in a fully executable program to provide an example in context and modified the regular expression (by adding * on the end) so that copy and paste works and it does not have the issue Uluk refers to. The solution seems pretty simple and will likely suffice for most purposes:

import java.util.regex.Pattern;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

public class NumericTextFieldTest extends Application {
  public static void main(String[] args) { launch(args); }

  @Override public void start(Stage stage) {
    TextField numberField = new TextField() {
      @Override public void replaceText(int start, int end, String text) {
        if (text.matches("[0-9]*")) {
          super.replaceText(start, end, text);
        }
      }

      @Override public void replaceSelection(String text) {
        if (text.matches("[0-9]*")) {
          super.replaceSelection(text);
        }
      }
    };

    stage.setScene(new Scene(numberField));
    stage.show();
  }
}

Alternate Solutions

You might also be interested in my alternate solution in JavaFX example of binding a slider value to a editable TextField. In that solution I extend TextField to expose an IntegerProperty on the field for simple binding purposes. The alternate solution is similar to that outlined by the original poster in their updated question (i.e. an event filter is added to restrict input data from key events), but additionally a ChangeListener is added on the TextField's text property to ensure that copy and pasted values are only accepted if they are numeric.

There are some other solutions to this question in the JavaFX Forum thread Numeric Textfield in JavaFX 2.0? which includes a reference to the number fields from FXExperience controls.