How to implement a NumberField in javaFX 2.0?
Asked Answered
S

8

23

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

Sera answered 5/12, 2011 at 5:31 Comment(1)
Post your solution as an answer to your question and mark it as accepted, please!Tangency
S
3

Found a solution. :)

public class NumFieldFX extends TextField {
   public NumFieldFX() {
      this.addEventFilter(KeyEvent.KEY_TYPED, new EventHandler<KeyEvent>() {
         public void handle( KeyEvent t ) {
            char ar[] = t.getCharacter().toCharArray();
            char ch = ar[t.getCharacter().toCharArray().length - 1];
            if (!(ch >= '0' && ch <= '9')) {
               System.out.println("The char you entered is not a number");
               t.consume();
            }
         }
      });
   }
}
Sera answered 10/12, 2015 at 4:35 Comment(0)
Y
17

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.

Yazbak answered 14/5, 2013 at 23:28 Comment(4)
This should be provided in javafx as a componentDefiance
@Defiance Java 8u40 introduced TextFormatter, which provides a general way to get this kind of functionality, though it's not all that well documented and it is not specific to only number fields. But yes, it would be nice if there were an out of the box component for such functionality either in the JavaFX library core (unlikely now there is already TextFormatter) or in a 3rd party extension library such as ControlsFX.Yazbak
@Yazbak NumberStringConverter didn't work for me. I suggest to try it.Cetinje
@Cetinje thanks for info, it's a little tricky. The NumberStringConverter as originally presented was only used as a formatter and not a filter, so it correctly extracts an output value, but does not alter the display of the input value or restrict what the user can type (which is what most people want), so I updated the answer to include a filter example with the TextFormatter and NumberStringConverter.Yazbak
A
3

There is a tip on FXExperience that deals with an issue just like that. To paraphrase, you extend the TextField and override the replaceText() and replaceSelection() methods, filtering all input that is not a number.

Once implemented, both methods should follow this template:

if (!newText.matches("[0-9]")) {
    super.call(allParams)
}
Accelerant answered 3/5, 2012 at 7:15 Comment(1)
However I have also confirmed that the TextField cannot avoid pasting a restricted value into it as discussed in that tutorial.Culosio
S
3

Found a solution. :)

public class NumFieldFX extends TextField {
   public NumFieldFX() {
      this.addEventFilter(KeyEvent.KEY_TYPED, new EventHandler<KeyEvent>() {
         public void handle( KeyEvent t ) {
            char ar[] = t.getCharacter().toCharArray();
            char ch = ar[t.getCharacter().toCharArray().length - 1];
            if (!(ch >= '0' && ch <= '9')) {
               System.out.println("The char you entered is not a number");
               t.consume();
            }
         }
      });
   }
}
Sera answered 10/12, 2015 at 4:35 Comment(0)
T
2

Here is the CustomText field I have written. It handles both Numbers only input and also maximumSize. Its a custom control that can be used in FXML and also the properties can be set in FXMl itself.

package fxml;

import javafx.beans.property.BooleanProperty; 
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.IntegerPropertyBase;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.TextField;

public class CustomTextField extends TextField {

/**
 * numericOnly property if set, will allow accept only numeric input.
 */
private BooleanProperty numericOnly = new SimpleBooleanProperty(this,
        "numericOnly", false);

public final boolean isNumericOnly() {
    return numericOnly.getValue();
}

public final void setNumericOnly(boolean value) {
    numericOnly.setValue(value);
}

public final BooleanProperty numericOnlyProperty() {
    return numericOnly;
}

/**
 * maxSize property , determines the maximum size of the text that can be
 * input.
 */
public IntegerProperty maxSize = new IntegerPropertyBase(1000) {

    @Override
    public String getName() {
        return "maxSize";
    }

    @Override
    public Object getBean() {
        return CustomTextField.this;
    }
};

public final IntegerProperty maxSizeProperty() {
    return maxSize;
};

public final int getMaxSize() {
    return maxSize.getValue();
}

public final void setMaxSize(int value) {
    maxSize.setValue(value);
}

/**
 * this method is called when user inputs text into the textField
 */
@Override
public void replaceText(int start, int end, String text) {
    if (numericOnly.getValue() && !text.equals("")) {
        if (!text.matches("[0-9]")) {
            return;
        }
    }
    if (getText().length() < getMaxSize() || text.equals("")) {
        super.replaceText(start, end, text);
    }
}

/**
 * this method is called when user pastes text into the textField
 */
@Override
public void replaceSelection(String text) {
    if (numericOnly.getValue() && !text.equals("")) {
        if (!text.matches("[0-9]+")) {
            return;
        }
    }
    super.replaceSelection(text);
    if (getText().length() > getMaxSize()) {
        String maxSubString = getText().substring(0, getMaxSize());
        setText(maxSubString);
        positionCaret(getMaxSize());
    }
}

}

Trademark answered 29/5, 2013 at 4:9 Comment(0)
S
2

For a number of TextFiled (include decimal point)

Arrays.asList(txtLongitude, txtLatitude, txtAltitude, txtSpeed, txtOrientation).forEach(textField ->
            textField.textProperty().addListener((observable, oldValue, newValue) ->
                    textField.setText(newValue.matches("^[0-9]*\\.?[0-9]*$") ? newValue : oldValue)
            ));
Stormproof answered 24/8, 2016 at 5:55 Comment(0)
H
1

Combined BaiJiFeiLong and AJAY PRAKASH solution to support decimal inputs

package com.mazeworks.cloudhms.view.components;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.IntegerPropertyBase;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.TextField;

public class NumericTextField extends TextField {

    /**
     * numericOnly property if set, will allow accept only numeric input.
     */
    private BooleanProperty numericOnly = new SimpleBooleanProperty(this,
            "numericOnly", false);

    public final boolean isNumericOnly() {
        return numericOnly.getValue();
    }

    public final void setNumericOnly(boolean value) {
        numericOnly.setValue(value);
    }

    public final BooleanProperty numericOnlyProperty() {
        return numericOnly;
    }

    /**
     * maxSize property, determines the maximum size of the text that 
     can be
     * input.
     */
    public IntegerProperty maxSize = new IntegerPropertyBase(1000) {

        @Override
        public String getName() {
            return "maxSize";
        }

        @Override
        public Object getBean() {
            return NumericTextField.this;
        }
    };

    public final IntegerProperty maxSizeProperty() {
        return maxSize;
    }

    ;

    public final int getMaxSize() {
        return maxSize.getValue();
    }

    public final void setMaxSize(int value) {
        maxSize.setValue(value);
    }

    /**
     * this method is called when user inputs text into the textField
     */
    @Override
    public void replaceText(int start, int end, String text) {
        if (numericOnly.getValue() && !text.equals("")) {
            if (!text.matches("^[0-9]*\\.?[0-9]*$")) {
                return;
            }
        }
        if (getText().length() < getMaxSize() || text.equals("")) {
            super.replaceText(start, end, text);
        }
    }

    /**
     * this method is called when user pastes text into the textField
     */
    @Override
    public void replaceSelection(String text) {
        if (numericOnly.getValue() && !text.equals("")) {
            if (!text.matches("^[0-9]*\\.?[0-9]*$")) {
                return;
            }
        }
        super.replaceSelection(text);
        if (getText().length() > getMaxSize()) {
            String maxSubString = getText().substring(0, getMaxSize());
            setText(maxSubString);
            positionCaret(getMaxSize());
        }
    }
}
Hatcher answered 8/5, 2018 at 12:4 Comment(0)
T
0

Inherit from TextField and override replaceText as such, to obtain a Double values only TextField:

@Override
public void replaceText(int start, int end, String text) {
    String preText = getText(0, start);
    String afterText = getText(end, getLength());
    String toBeEnteredText = preText + text + afterText;

    // Treat the case where the user inputs proper text and is not inputting backspaces or other control characters
    // which would be represented by an empty text argument:
    if (!text.isEmpty() && text.matches("\\d|\\.")) {
        Logger.getAnonymousLogger().info("Paring non-empty.");
        try {
            Logger.getAnonymousLogger().info("Parsing " + toBeEnteredText);
            Double.parseDouble(toBeEnteredText);
            super.replaceText(start, end, text);
        } catch (Exception ignored) {
        }
    }

    // If the user used backspace or del, the result text is impossible to not parse as a Double/Integer so just
    // enter that text right ahead:
    if (text.isEmpty()) {
        super.replaceText(start, end, text);
    }
}
Tympanist answered 15/9, 2015 at 10:0 Comment(0)
G
0

Applying the following way it is possible to make TextField into NumberField but this way it will not filter the keyboard input instantly... but if you focus out the field then you will see the result. You may use this for basic numeric input field validation. I am giving here some example... It may help someone :)

// It will show the number with comma ie. 64,568,455
TextField numberField = new TextField();
numberField.setTextFormatter(new TextFormatter<>(new NumberStringConverter()));

// It will show the integer number ie. 64568455
TextField integerField = new TextField();
integerField.setTextFormatter(new TextFormatter<>(new IntegerStringConverter()));

// It will ensure the float format ie. 2635.0
TextField floatField = new TextField();
floatField.setTextFormatter(new TextFormatter<>(new FloatStringConverter()));
Gast answered 27/7, 2020 at 4:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.