¿Cuál es la forma recomendada de hacer un TextField numérico en JavaFX?

Necesito restringir la entrada en un TextField a enteros. ¿Algún consejo?

Hilo muy antiguo, pero parece más nítido y elimina caracteres no numéricos si se pegan.

// force the field to be numeric only textField.textProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, String oldValue, String newValue) { if (!newValue.matches("\\d*")) { textField.setText(newValue.replaceAll("[^\\d]", "")); } } }); 

Sé que este es un hilo bastante viejo, pero para los lectores del futuro aquí hay otra solución que encuentro bastante intuitiva:

 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]*"); } } 

Editar: Gracias none_ y SCBoy por sus mejoras sugeridas.

Actualización de abril de 2016

Esta respuesta se creó hace algunos años y ahora la respuesta original está en gran parte obsoleta.

Desde Java 8u40, Java tiene un TextFormatter que generalmente es el mejor para imponer la entrada de formatos específicos como los numéricos en JavaFX TextFields:

  • Java 8 U40 TextFormatter (JavaFX) para restringir la entrada del usuario solo para el número decimal
  • Cadena con números y letras para duplicar javafx

Ver también otras respuestas a esta pregunta que mencionan específicamente TextFormatter.


Respuesta original

Hay algunos ejemplos de esto en esta esencia , he duplicado uno de los siguientes ejemplos:

 // 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 (maxValue < minValue) throw new IllegalArgumentException( "IntField max value " + minValue + " less than min 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() { @Override public void changed(ObservableValue 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() { @Override public void handle(KeyEvent keyEvent) { if (!"0123456789".contains(keyEvent.getCharacter())) { keyEvent.consume(); } } }); // ensure any entered values lie inside the required range. this.textProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observableValue, String oldValue, String newValue) { if (newValue == null || "".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())); } }); } } 

TextInput tiene un TextFormatter que se puede usar para formatear, convertir y limitar los tipos de texto que se pueden ingresar.

TextFormatter tiene un filtro que puede usarse para rechazar entradas. Necesitamos establecer esto para rechazar cualquier cosa que no sea un número entero válido. También tiene un convertidor que debemos configurar para convertir el valor de la cadena a un valor entero que podemos vincular más adelante.

Permite crear un filtro reutilizable:

 public class IntegerFilter implements UnaryOperator { 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; } } 

El filtro puede hacer una de tres cosas, puede devolver el cambio sin modificar para aceptarlo tal como está, puede alterar el cambio de alguna forma que considere adecuada o puede devolver null para rechazar el cambio todos juntos.

Usaremos el IntegerStringConverter estándar como un convertidor.

Poniéndolo todo junto tenemos:

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

Si lo que quieres es no necesitar un filtro reutilizable, puedes hacer este lujoso trazador de líneas en su lugar:

 TextFormatter formatter = new TextFormatter<>( new IntegerStringConverter(), defaultValue, c -> Pattern.matches("\\d*", c.getText()) ? c : null ); 

A partir de JavaFX 8u40, puede establecer un objeto TextFormatter en un campo de texto:

 UnaryOperator filter = change -> { String text = change.getText(); if (text.matches("[0-9]*")) { return change; } return null; }; TextFormatter textFormatter = new TextFormatter<>(filter); fieldNport = new TextField(); fieldNport.setTextFormatter(textFormatter); 

Esto evita eventos de subclases y cambios duplicados que obtendrá cuando agregue un detector de cambios a la propiedad de texto y modifique el texto en ese oyente.

No me gustan las excepciones, así que utilicé la función de matches de String-Class

 text.textProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, String oldValue, String newValue) { if (newValue.matches("\\d*")) { int value = Integer.parseInt(newValue); } else { text.setText(oldValue); } } }); 
 TextField text = new TextField(); text.textProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue 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); } } }); 

La cláusula if es importante para manejar entradas como 0.5d o 0.7f que son analizadas correctamente por Int.parseInt (), pero que no deberían aparecer en el campo de texto.

A partir de Java SE 8u40 , para tal necesidad puede usar un Spinnerentero ” que permite seleccionar de manera segura un entero válido usando las teclas de flecha arriba / abajo del teclado o los botones de flecha arriba / abajo proporcionados.

También puede definir un mínimo , un máximo y un valor inicial para limitar los valores permitidos y una cantidad para incrementar o disminuir por, por paso.

Por ejemplo

 // Creates an integer spinner with 1 as min, 10 as max and 2 as initial value Spinner 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 spinner2 = new Spinner<>(0, 100, 10, 10); 

Ejemplo de resultado con un spinner ” entero ” y un spinner ” doble

enter image description here

Un spinner es un control de campo de texto de una sola línea que permite al usuario seleccionar un número o un valor de objeto a partir de una secuencia ordenada de dichos valores. Por lo general, los rotadores proporcionan un par de pequeños botones de flecha para recorrer los elementos de la secuencia. Las teclas de flecha arriba / abajo del teclado también se desplazan por los elementos. También se le puede permitir al usuario escribir un valor (legal) directamente en la ruleta. Aunque los cuadros combinados proporcionan una funcionalidad similar, los rotuladores a veces son preferidos porque no requieren una lista desplegable que pueda oscurecer los datos importantes, y también porque permiten características tales como ajustar el valor máximo al valor mínimo (por ejemplo, del mayor entero positivo a 0).

Más detalles sobre el control Spinner

La respuesta preffered puede ser incluso más pequeña si utiliza Java 1.8 Lambdas

 textfield.textProperty().addListener((observable, oldValue, newValue) -> { if (!newValue.matches("\\d*")) { textfield.setText(newValue.replaceAll("[^\\d]", "")); } }); 

Éste trabajó para mí.

 public void RestrictNumbersOnly(TextField tf){ tf.textProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, String oldValue, String newValue) { if (!newValue.matches("|[-\\+]?|[-\\+]?\\d+\\.?|[-\\+]?\\d+\\.?\\d+")){ tf.setText(oldValue); } } }); } 

Esto es lo que uso:

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

Lo mismo en la notación lambda sería:

 private TextField textField; textField.textProperty().addListener((observable, oldValue, newValue) -> { if(!newValue.matches("[0-9]*")){ textField.setText(oldValue); } }); 

Si desea aplicar el mismo oyente a más de un TextField, aquí está la solución más simple:

 TextField txtMinPrice, txtMaxPrice = new TextField(); ChangeListener forceNumberListener = (observable, oldValue, newValue) -> { if (!newValue.matches("\\d*")) ((StringProperty) observable).set(oldValue); }; txtMinPrice.textProperty().addListener(forceNumberListener); txtMaxPrice.textProperty().addListener(forceNumberListener); 

Pruebe este código simple que hará el trabajo.

 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; } })); 

Este método permite que TextField termine todo el proceso (copiar / pegar / deshacer seguro). No requiere extender clases y le permite decidir qué hacer con el nuevo texto después de cada cambio (para llevarlo a la lógica, o volver al valor anterior, o incluso modificarlo).

  // 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()); } ); 

Para su caso solo agregue esta lógica adentro. Funciona perfectamente.

  if (newValue.equals("")) return; try { Integer i = Integer.valueOf(newValue); // do what you want with this i } catch (Exception e) { ((StringProperty)observable).setValue(oldValue); } 

Aquí hay una clase simple que maneja algunas validaciones básicas en TextField , usando TextFormatter introducido en JavaFX 8u40

EDITAR:

(Código agregado con respecto al comentario de Floern)

 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 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); } } 

Puedes usarlo así:

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

o puede crear una instancia en un archivo fxml y aplicarlo a un CustomTextField con las propiedades correspondientes.

app.fxml:

    

CustomTextField.class:

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

Código en github

Este código funciona bien para mí incluso si intentas copiar / pegar.

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

Mmmm. Me encontré con ese problema hace semanas. Como la API no proporciona un control para lograr eso,
es posible que desee utilizar el suyo propio. Usé algo como:

 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()} } 

Se usa de la misma manera que un TextBox normal,
pero también tiene un atributo de value que almacena el número entero ingresado.
Cuando el control pierde el foco, valida el valor y lo revierte (si no es válido).

este código Haga que su campo de texto acepte solo el número

 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)); } } }); 

En las actualizaciones recientes de JavaFX, debe establecer el texto nuevo en el método Platform.runLater de la siguiente manera:

  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) { } } 

También es una buena idea establecer la posición de intercalación.

Me gustaría mejorar la respuesta de Evan Knowles: https://stackoverflow.com/a/30796829/2628125

En mi caso, tuve clases con controladores para la parte de UI Component. Inicialización:

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

Y el método de la sanitización numérica:

 private synchronized void numericSanitization(ObservableValue observable, String oldValue, String newValue) { final String allowedPattern = "\\d*"; if (!newValue.matches(allowedPattern)) { this.dataText.setText(oldValue); } } 

La palabra clave sincronizada se agrega para evitar posibles problemas de locking de representación en javafx si se llamará a setText antes de que se termine de ejecutar el anterior. Es fácil de reproducir si comienza a escribir caracteres incorrectos muy rápido.

Otra ventaja es que solo conservas un patrón para que coincida y simplemente lo retrotrae. Es mejor porque puede abstraer fácilmente la solución para diferentes patrones de desinfección.

  rate_text.textProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue 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); } }); 

Esto funciona bien, ya que le permite ingresar solo el valor entero y el valor decimal (que tiene el código ASCII 46).