What is the recommended way to make a numeric TextField in JavaFX?
Asked Answered
H

24

106

I need to restrict input into a TextField to integers. Any advice?

Higdon answered 26/9, 2011 at 13:10 Comment(1)
Note: listening to the textProperty are wrong! That was the best that could be done before having a TextFormatter (since fx8u40 or so). Since then, the basic rule applies: In general is is considered bad practice to modify the observed value in this method and using a TextFormatter is the only acceptable solution.Prefix
A
147

Very old thread, but this seems neater and strips out non-numeric characters if pasted.

// force the field to be numeric only
textField.textProperty().addListener(new ChangeListener<String>() {
    @Override
    public void changed(ObservableValue<? extends String> observable, String oldValue, 
        String newValue) {
        if (!newValue.matches("\\d*")) {
            textField.setText(newValue.replaceAll("[^\\d]", ""));
        }
    }
});
Amosamount answered 12/6, 2015 at 6:43 Comment(8)
Works wonderfully. Note that you can also use \\D+ (or just \\D) instead of [^\\d], if you want to save a few characters.Jujitsu
Even simpler is to set the text back to oldValue. That way you do not have to mess around with regex replacements (which did not work for me at all unfortunately).Statutory
@Statutory This does give the option of stripping non-numerics from pasted text though.Amosamount
Oh indeed! I forgot that... However I could not get the replace to work for we with neither of the replacement combinations so I went to this way. The replace did exactly nothing (did not replace anything). Neither with your code nor the suggestion of ricky.Statutory
Maybe that's the topic for another question, but what should I write instead of \\d* and [^\\d] if I want to force a double input instead? @EvanKnowlesDubai
Out of date, see TextFormatter answer below.Sleuth
Could also possibly just use Integer.parseInt(newValue) and use try and catch to spot an error on NumberFormatExceptionTrellas
For slightly less boilerplate use a lambda expression: textField.textProperty().addListener((ObservableValue<? extends String> observable, String oldValue, String newValue) -> { if (!newValue.matches("\\d*")) { textField.setText(newValue.replaceAll("[^\\d]", ""));}});Ipomoea
B
58

javafx.scene.control.TextFormatter

Updated Apr 2016

This answer was created some years ago and the original answer is largely obsolete now.

Since Java 8u40, Java has a TextFormatter which is usually best for enforcing input of specific formats such as numerics on JavaFX TextFields:

See also other answers to this question which specifically mention TextFormatter.


Original Answer

There are some examples of this in this gist, I have duplicated one of the examples below:

// helper text field subclass which restricts text input to a given range of natural int numbers
// and exposes the current numeric int value of the edit box as a value property.
class IntField extends TextField {
  final private IntegerProperty value;
  final private int minValue;
  final private int maxValue;
 
  // expose an integer value property for the text field.
  public int  getValue()                 { return value.getValue(); }
  public void setValue(int newValue)     { value.setValue(newValue); }
  public IntegerProperty valueProperty() { return value; }
    
  IntField(int minValue, int maxValue, int initialValue) {
    if (minValue > maxValue) 
      throw new IllegalArgumentException(
        "IntField min value " + minValue + " greater than max value " + maxValue
      );
    if (!((minValue <= initialValue) && (initialValue <= maxValue))) 
      throw new IllegalArgumentException(
        "IntField initialValue " + initialValue + " not between " + minValue + " and " + maxValue
      );
 
    // initialize the field values.
    this.minValue = minValue;
    this.maxValue = maxValue;
    value = new SimpleIntegerProperty(initialValue);
    setText(initialValue + "");
 
    final IntField intField = this;
 
    // make sure the value property is clamped to the required range
    // and update the field's text to be in sync with the value.
    value.addListener(new ChangeListener<Number>() {
      @Override public void changed(ObservableValue<? extends Number> observableValue, Number oldValue, Number newValue) {
        if (newValue == null) {
          intField.setText("");
        } else {
          if (newValue.intValue() < intField.minValue) {
            value.setValue(intField.minValue);
            return;
          }
 
          if (newValue.intValue() > intField.maxValue) {
            value.setValue(intField.maxValue);
            return;
          }
 
          if (newValue.intValue() == 0 && (textProperty().get() == null || "".equals(textProperty().get()))) {
            // no action required, text property is already blank, we don't need to set it to 0.
          } else {
            intField.setText(newValue.toString());
          }
        }
      }
    });
 
    // restrict key input to numerals.
    this.addEventFilter(KeyEvent.KEY_TYPED, new EventHandler<KeyEvent>() {
      @Override public void handle(KeyEvent keyEvent) {
        if(intField.minValue<0) {
                if (!"-0123456789".contains(keyEvent.getCharacter())) {
                    keyEvent.consume();
                }
            }
            else {
                if (!"0123456789".contains(keyEvent.getCharacter())) {
                    keyEvent.consume();
                }
            }
      }
    });
      
    // ensure any entered values lie inside the required range.
    this.textProperty().addListener(new ChangeListener<String>() {
      @Override public void changed(ObservableValue<? extends String> observableValue, String oldValue, String newValue) {
        if (newValue == null || "".equals(newValue) || (intField.minValue<0 && "-".equals(newValue))) {
          value.setValue(0);
          return;
        }
 
        final int intValue = Integer.parseInt(newValue);
 
        if (intField.minValue > intValue || intValue > intField.maxValue) {
          textProperty().setValue(oldValue);
        }
          
        value.set(Integer.parseInt(textProperty().get()));
      }
    });
  }
}
Boff answered 12/10, 2012 at 1:52 Comment(2)
doesn't minValue > maxValue imply that maxValue < minValue? Also, "IntField max value " + minValue + " less than min value " + maxValue should be "IntField max value " + maxValue + " less than min value " + minValueEshelman
Yes, I edited the code to remove the redundant statement with the incorrect message.Boff
K
44

I know this is a rather old thread, but for future readers here is another solution I found quite intuitive:

public class NumberTextField extends TextField
{

    @Override
    public void replaceText(int start, int end, String text)
    {
        if (validate(text))
        {
            super.replaceText(start, end, text);
        }
    }

    @Override
    public void replaceSelection(String text)
    {
        if (validate(text))
        {
            super.replaceSelection(text);
        }
    }

    private boolean validate(String text)
    {
        return text.matches("[0-9]*");
    }
}

Edit: Thanks none_ and SCBoy for your suggested improvements.

Kimberlite answered 23/9, 2013 at 12:36 Comment(3)
Does anyone know what regex to use for decimal numbers (e.g. 123.456)? The value of the validate(text) function parameter "text" is the last user-edited character, not the entire string in the text field, therefore the regex may not match correctly. For example I am using this command like so: text.matches("\\d+"); and I cannot delete any characters in the text fieldKovacev
@Kovacev Look here and here.Clymer
I think that this is a good solution, but I would change one thing. Instead of writing text.matches("[0-9]*") I would create variable for pattern and compile it. In your code pattern is compiled every time when someone write character in a field, and this is costly for CPU and memory. So I would add variable: private static Pattern integerPattern = Pattern.compile("[0-9]*"); And replace validate body with: integerPattern.matcher(text).matches();Duvalier
B
42

Starting with JavaFX 8u40, you can set a TextFormatter object on a text field:

UnaryOperator<Change> filter = change -> {
    String text = change.getText();

    if (text.matches("[0-9]*")) {
        return change;
    }

    return null;
};
TextFormatter<String> textFormatter = new TextFormatter<>(filter);
fieldNport = new TextField();
fieldNport.setTextFormatter(textFormatter);

This avoids both subclassing and duplicate change events which you will get when you add a change listener to the text property and modify the text in that listener.

Belted answered 5/4, 2016 at 20:18 Comment(1)
This is bad. If you use getText as above, you can't enter control characters into the TextField, e.g. backspace, arrows, etc. Do it like this instead: control.setTextFormatter(new TextFormatter <> (change -> change.getControlNewText().matches(regex) ? change : null));Lunatic
F
33

The TextInput has a TextFormatter which can be used to format, convert and limit the types of text that can be input.

The TextFormatter has a filter which can be used to reject input. We need to set this to reject anything that's not a valid integer. It also has a converter which we need to set to convert the string value to an integer value which we can bind later on.

Lets create a reusable filter:

public class IntegerFilter implements UnaryOperator<TextFormatter.Change> {
    private final static Pattern DIGIT_PATTERN = Pattern.compile("\\d*");

    @Override
    public Change apply(TextFormatter.Change aT) {
        return DIGIT_PATTERN.matcher(aT.getText()).matches() ? aT : null;
    }
}

The filter can do one of three things, it can return the change unmodified to accept it as it is, it can alter the change in some way it deems fit or it can return null to reject the change all together.

We will use the standard IntegerStringConverter as a converter.

Putting it all together we have:

TextField textField = ...;

TextFormatter<Integer> formatter = new TextFormatter<>(
    new IntegerStringConverter(), // Standard converter form JavaFX
    defaultValue, 
    new IntegerFilter());
formatter.valueProperty().bindBidirectional(myIntegerProperty);

textField.setTextFormatter(formatter);

If you want don't need a reusable filter you can do this fancy one-liner instead:

TextFormatter<Integer> formatter = new TextFormatter<>(
    new IntegerStringConverter(), 
    defaultValue,  
    c -> Pattern.matches("\\d*", c.getText()) ? c : null );
Furriery answered 20/4, 2016 at 16:18 Comment(0)
F
21

I don't like exceptions thus I used the matches function from String-Class

text.textProperty().addListener(new ChangeListener<String>() {
    @Override
    public void changed(ObservableValue<? extends String> observable, String oldValue, 
        String newValue) {
        if (newValue.matches("\\d*")) {
            int value = Integer.parseInt(newValue);
        } else {
            text.setText(oldValue);
        }
    }
});
Fulgent answered 30/9, 2013 at 17:9 Comment(5)
sometimes you have an exception, because the nuber could be too large.Demonetize
Tip: replace the regex "\\d+" to "\\d*". This will allow you to remove all characters from the text input field (use backspace/delete to clear the field).Twelvemonth
Don't forget to set the caret position after setting it back to the old value: textField.positionCaret(textField.getLength());Kimbrough
Change the fist if condition to :if (newValue.matches("\\d*") && newValue.getText().length < 5) if you want to limit the input to 4 digits in this case.Materialism
@Cappuccino90 You should switch the statements because length checking is much cheaper on each hit than parsing a regexFulgent
M
13

Starting from Java SE 8u40, for such need you can use an "integer" Spinner allowing to safely select a valid integer by using the keyboard's up arrow/down arrow keys or the up arrow/down arrow provided buttons.

You can also define a min, a max and an initial value to limit the allowed values and an amount to increment or decrement by, per step.

For example

// Creates an integer spinner with 1 as min, 10 as max and 2 as initial value
Spinner<Integer> spinner1 = new Spinner<>(1, 10, 2);
// Creates an integer spinner with 0 as min, 100 as max and 10 as initial 
// value and 10 as amount to increment or decrement by, per step
Spinner<Integer> spinner2 = new Spinner<>(0, 100, 10, 10);

Example of result with an "integer" spinner and a "double" spinner

enter image description here

A spinner is a single-line text field control that lets the user select a number or an object value from an ordered sequence of such values. Spinners typically provide a pair of tiny arrow buttons for stepping through the elements of the sequence. The keyboard's up arrow/down arrow keys also cycle through the elements. The user may also be allowed to type a (legal) value directly into the spinner. Although combo boxes provide similar functionality, spinners are sometimes preferred because they don't require a drop-down list that can obscure important data, and also because they allow for features such as wrapping from the maximum value back to the minimum value (e.g., from the largest positive integer to 0).

More details about the Spinner control

Madness answered 24/3, 2017 at 14:35 Comment(2)
People should not the spinner doesn't validate on text entry or lost focusOsborne
if the spinner included a textformatter to filter out non-digits, it would be ideal, but like that it is rather unhandySclerosis
O
12

The preferred answer can be even smaller if you make use of Java 1.8 Lambdas

textfield.textProperty().addListener((observable, oldValue, newValue) -> {
    if (newValue.matches("\\d*")) return;
    textfield.setText(newValue.replaceAll("[^\\d]", ""));
});
Outstretch answered 14/3, 2017 at 8:11 Comment(1)
thank my friend, this answer dont generate a problem in ouput terminalCrustacean
J
8

I want to help with my idea from combining Evan Knowles answer with TextFormatter from JavaFX 8

textField.setTextFormatter(new TextFormatter<>(c -> {
    if (!c.getControlNewText().matches("\\d*")) 
        return null;
    else
        return c;
    }
));

so good luck ;) keep calm and code java

Jaynajayne answered 20/12, 2018 at 21:57 Comment(0)
P
6
TextField text = new TextField();

text.textProperty().addListener(new ChangeListener<String>() {
    @Override
    public void changed(ObservableValue<? extends String> observable,
            String oldValue, String newValue) {
        try {
            Integer.parseInt(newValue);
            if (newValue.endsWith("f") || newValue.endsWith("d")) {
                manualPriceInput.setText(newValue.substring(0, newValue.length()-1));
            }
        } catch (ParseException e) {
            text.setText(oldValue);
        }
    }
});

The if clause is important to handle inputs like 0.5d or 0.7f which are correctly parsed by Int.parseInt(), but shouldn't appear in the text field.

Preemption answered 14/6, 2012 at 11:16 Comment(1)
Woot? Since when is 0.5d correctly parsed by Integer.parseInt()?! It throws a java.lang.NumberFormatException: For input string: "0.5d" (as expected, since it is not an Integer)Kimberlite
C
4

If you want to apply the same listener to more than one TextField here is the simplest solution:

TextField txtMinPrice, txtMaxPrice = new TextField();

ChangeListener<String> forceNumberListener = (observable, oldValue, newValue) -> {
    if (!newValue.matches("\\d*"))
      ((StringProperty) observable).set(oldValue);
};

txtMinPrice.textProperty().addListener(forceNumberListener);
txtMaxPrice.textProperty().addListener(forceNumberListener);
Chalcanthite answered 21/5, 2016 at 8:35 Comment(0)
A
4

Try this simple code it will do the job.

DecimalFormat format = new DecimalFormat( "#.0" );
TextField field = new TextField();
field.setTextFormatter( new TextFormatter<>(c ->
{
    if ( c.getControlNewText().isEmpty() )
    {
        return c;
    }

    ParsePosition parsePosition = new ParsePosition( 0 );
    Object object = format.parse( c.getControlNewText(), parsePosition );

    if ( object == null || parsePosition.getIndex() <          c.getControlNewText().length() )
    {
        return null;
    }
    else
    {
        return c;
    }
}));
Aggy answered 25/11, 2016 at 22:15 Comment(0)
M
3

This one worked for me.

public void RestrictNumbersOnly(TextField tf){
    tf.textProperty().addListener(new ChangeListener<String>() {
        @Override
        public void changed(ObservableValue<? extends String> observable, String oldValue, 
            String newValue) {
            if (!newValue.matches("|[-\\+]?|[-\\+]?\\d+\\.?|[-\\+]?\\d+\\.?\\d+")){
                tf.setText(oldValue);
            }
        }
    });
}
Martel answered 19/4, 2016 at 13:2 Comment(0)
C
2

This method lets TextField to finish all processing (copy/paste/undo safe). Does not require to extend classes and allows you to decide what to do with new text after every change (to push it to logic, or turn back to previous value, or even to modify it).

  // fired by every text property change
textField.textProperty().addListener(
  (observable, oldValue, newValue) -> {
    // Your validation rules, anything you like
      // (! note 1 !) make sure that empty string (newValue.equals("")) 
      //   or initial text is always valid
      //   to prevent inifinity cycle
    // do whatever you want with newValue

    // If newValue is not valid for your rules
    ((StringProperty)observable).setValue(oldValue);
      // (! note 2 !) do not bind textProperty (textProperty().bind(someProperty))
      //   to anything in your code.  TextProperty implementation
      //   of StringProperty in TextFieldControl
      //   will throw RuntimeException in this case on setValue(string) call.
      //   Or catch and handle this exception.

    // If you want to change something in text
      // When it is valid for you with some changes that can be automated.
      // For example change it to upper case
    ((StringProperty)observable).setValue(newValue.toUpperCase());
  }
);

For your case just add this logic inside. Works perfectly.

   if (newValue.equals("")) return; 
   try {
     Integer i = Integer.valueOf(newValue);
     // do what you want with this i
   } catch (Exception e) {
     ((StringProperty)observable).setValue(oldValue);
   }
Cychosz answered 31/10, 2014 at 8:37 Comment(0)
K
2

Here is a simple class that handles some basic validations on TextField, using TextFormatter introduced in JavaFX 8u40

EDIT:

(Code added regarding Floern's comment)

import java.text.DecimalFormatSymbols;
import java.util.regex.Pattern;

import javafx.beans.NamedArg;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;

public class TextFieldValidator {

    private static final String CURRENCY_SYMBOL   = DecimalFormatSymbols.getInstance().getCurrencySymbol();
    private static final char   DECIMAL_SEPARATOR = DecimalFormatSymbols.getInstance().getDecimalSeparator();

    private final Pattern       INPUT_PATTERN;

    public TextFieldValidator(@NamedArg("modus") ValidationModus modus, @NamedArg("countOf") int countOf) {
        this(modus.createPattern(countOf));
    }

    public TextFieldValidator(@NamedArg("regex") String regex) {
        this(Pattern.compile(regex));
    }

    public TextFieldValidator(Pattern inputPattern) {
        INPUT_PATTERN = inputPattern;
    }

    public static TextFieldValidator maxFractionDigits(int countOf) {
        return new TextFieldValidator(maxFractionPattern(countOf));
    }

    public static TextFieldValidator maxIntegers(int countOf) {
        return new TextFieldValidator(maxIntegerPattern(countOf));
    }

    public static TextFieldValidator integersOnly() {
        return new TextFieldValidator(integersOnlyPattern());
    }

    public TextFormatter<Object> getFormatter() {
        return new TextFormatter<>(this::validateChange);
    }

    private Change validateChange(Change c) {
        if (validate(c.getControlNewText())) {
            return c;
        }
        return null;
    }

    public boolean validate(String input) {
        return INPUT_PATTERN.matcher(input).matches();
    }

    private static Pattern maxFractionPattern(int countOf) {
        return Pattern.compile("\\d*(\\" + DECIMAL_SEPARATOR + "\\d{0," + countOf + "})?");
    }

    private static Pattern maxCurrencyFractionPattern(int countOf) {
        return Pattern.compile("^\\" + CURRENCY_SYMBOL + "?\\s?\\d*(\\" + DECIMAL_SEPARATOR + "\\d{0," + countOf + "})?\\s?\\" +
                CURRENCY_SYMBOL + "?");
    }

    private static Pattern maxIntegerPattern(int countOf) {
        return Pattern.compile("\\d{0," + countOf + "}");
    }

    private static Pattern integersOnlyPattern() {
        return Pattern.compile("\\d*");
    }

    public enum ValidationModus {

        MAX_CURRENCY_FRACTION_DIGITS {
            @Override
            public Pattern createPattern(int countOf) {
                return maxCurrencyFractionPattern(countOf);
            }
        },

        MAX_FRACTION_DIGITS {
            @Override
            public Pattern createPattern(int countOf) {
                return maxFractionPattern(countOf);
            }
        },
        MAX_INTEGERS {
            @Override
            public Pattern createPattern(int countOf) {
                return maxIntegerPattern(countOf);
            }
        },

        INTEGERS_ONLY {
            @Override
            public Pattern createPattern(int countOf) {
                return integersOnlyPattern();
            }
        };

        public abstract Pattern createPattern(int countOf);
    }

}

You can use it like this:

textField.setTextFormatter(new TextFieldValidator(ValidationModus.INTEGERS_ONLY).getFormatter());

or you can instantiate it in a fxml file, and apply it to a customTextField with the according properties.

app.fxml:

<fx:define>
    <TextFieldValidator fx:id="validator" modus="INTEGERS_ONLY"/>
</fx:define>

CustomTextField.class:

public class CustomTextField {

private TextField textField;

public CustomTextField(@NamedArg("validator") TextFieldValidator validator) {
        this();
        textField.setTextFormatter(validator.getFormatter());
    }
}

Code on github

Kozhikode answered 5/4, 2016 at 20:33 Comment(0)
S
2

This is what I use:

private TextField textField;
textField.textProperty().addListener(new ChangeListener<String>() {
    @Override
    public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
        if(!newValue.matches("[0-9]*")){
            textField.setText(oldValue);
        }

    }
});

The same in lambda notation would be:

private TextField textField;
textField.textProperty().addListener((observable, oldValue, newValue) -> {
    if(!newValue.matches("[0-9]*")){
        textField.setText(oldValue);
    }
});
Swetiana answered 12/5, 2016 at 8:14 Comment(0)
L
0

Mmmm. I ran into that problem weeks ago. As the API doesn't provide a control to achieve that,
you may want to use your own one.
I used something like:

public class IntegerBox extends TextBox {
    public-init var value : Integer = 0;
    protected function apply() {
        try {
            value = Integer.parseInt(text);
        } catch (e : NumberFormatException) {}
        text = "{value}";
    }
    override var focused = false on replace {apply()};
    override var action = function () {apply()}
}

It's used the same way that a normal TextBox,
but has also a value attribute which stores the entered integer.
When the control looses the focus, it validates the value and reverts it (if isn't valid).

Lectionary answered 12/10, 2011 at 15:4 Comment(2)
what language is this?Seek
It's JavaFX Script Rabbi, it's obsolete.Boff
R
0

this Code Make your textField Accept only Number

textField.lengthProperty().addListener((observable, oldValue, newValue) -> {
        if(newValue.intValue() > oldValue.intValue()){
            char c = textField.getText().charAt(oldValue.intValue());
            /** Check if the new character is the number or other's */
            if( c > '9' || c < '0'){
                /** if it's not number then just setText to previous one */
                textField.setText(textField.getText().substring(0,textField.getText().length()-1));
            }
        }
    });
Rivarivage answered 25/4, 2016 at 22:35 Comment(0)
U
0

This code works fine for me even if you try to copy/paste.

myTextField.textProperty().addListener((observable, oldValue, newValue) -> {
    if (!newValue.matches("\\d*")) {
        myTextField.setText(oldValue);

    }
});
Uptake answered 21/3, 2018 at 1:34 Comment(0)
M
-1

In recent updates of JavaFX, you have to set new text in Platform.runLater method just like this:

    private void set_normal_number(TextField textField, String oldValue, String newValue) {
    try {
        int p = textField.getCaretPosition();
        if (!newValue.matches("\\d*")) {
            Platform.runLater(() -> {
                textField.setText(newValue.replaceAll("[^\\d]", ""));
                textField.positionCaret(p);
            });
        }
    } catch (Exception e) {
    }
}

It's a good idea to set caret position too.

Mnemonic answered 14/5, 2017 at 15:11 Comment(2)
Thank you for your answer! Could you explain a little bit why Platform.runLater is needed?Algo
@Algo in recent version of JavaFX, it will throw exception if you don't use Platform.runLaterMnemonic
N
-1

I would like to improve Evan Knowles answer: https://mcmap.net/q/203421/-what-is-the-recommended-way-to-make-a-numeric-textfield-in-javafx

In my case I had class with handlers for UI Component part. Initialization:

this.dataText.textProperty().addListener((observable, oldValue, newValue) -> this.numericSanitization(observable, oldValue, newValue));

And the numbericSanitization method:

private synchronized void numericSanitization(ObservableValue<? extends String> observable, String oldValue, String newValue) {
    final String allowedPattern = "\\d*";

    if (!newValue.matches(allowedPattern)) {
        this.dataText.setText(oldValue);
    }
}

Keyword synchronized is added to prevent possible render lock issue in javafx if setText will be called before old one is finished execution. It is easy to reproduce if you will start typing wrong chars really fast.

Another advantage is that you keep only one pattern to match and just do rollback. It is better because you can easily abstragate solution for different sanitization patterns.

Neuroglia answered 20/5, 2017 at 15:37 Comment(0)
C
-1
rate_text.textProperty().addListener(new ChangeListener<String>() {

    @Override
    public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
        String s="";
        for(char c : newValue.toCharArray()){
            if(((int)c >= 48 && (int)c <= 57 || (int)c == 46)){
                s+=c;
            }
        }
        rate_text.setText(s);
    }
});

This works fine as it allows you to enter only integer value and decimal value (having ASCII code 46).

Caniff answered 13/6, 2017 at 6:18 Comment(0)
A
-2

A little late, but if you also what to include decimals:

  public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
            if (!newValue.matches("\\d{0,7}([\\.]\\d{0,4})?")) {
                textField.setText(oldValue);
            }
        }
Aracelyaraceous answered 17/4, 2021 at 4:47 Comment(1)
no - it's basically wrong to change the text in a listener to the textPropertyPrefix
H
-2

Another very simple solution would be:

    TextField tf = new TextField();
    tf.addEventFilter(KeyEvent.ANY, event -> {
        if (!event.getCharacter().trim().matches("\\d?")) {
            event.consume();
        }
    });
Hi answered 8/5, 2021 at 11:52 Comment(3)
no .. that's plain wrongs: not all changes happen via keyboard input - so you might end up with invalid state in the textfield. As I already commented: since fx8u40 the correct way to handle constraints is a TextFormatter.Prefix
yes, no doubt, TextFormatter is the best and recommended approach. Interestingly my answer is not even correct before fx8u40, cause of invalid states you mentioned.Hi
But keep in mind, there is a lot of legagy code out there in the real world and sometimes you have still pre fx8 versions running (have to deal with this often, especially in financial legacy environments). So people need alternatives and a ChangeListener is a valid working solution for these circumstances. I think it is definitely not justified or correct to downvote those answers which relate to the ChangeListener-solutions, cause your assumption is based on a concrete java version ...Hi

© 2022 - 2024 — McMap. All rights reserved.