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


85

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


3
Nota: ¡escuchar el texto de la propiedad es incorrecto ! Eso era lo mejor que se podía hacer antes de tener un TextFormatter (desde fx8u40 más o menos). Desde entonces, se aplica la regla básica: en general se considera una mala práctica modificar el valor observado en este método y usar un TextFormatter es la única solución aceptable.
kleopatra

Respuestas:


118

Hilo muy antiguo, pero parece más ordenado y elimina los caracteres no numéricos si se pega.

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

3
Funciona de maravilla. Tenga en cuenta que también puede usar \\D+(o simplemente \\D) en lugar de [^\\d], si desea guardar algunos caracteres.
ricky3350

5
Aún más sencillo es volver a establecer el texto en oldValue. De esa manera, no tiene que perder el tiempo con reemplazos de expresiones regulares (que desafortunadamente no funcionó para mí).
geisterfurz007

@ geisterfurz007 Sin embargo, esto da la opción de eliminar los no numéricos del texto pegado.
Evan Knowles

10
Desactualizado, consulte la respuesta de TextFormatter a continuación.
gerardw

3
También podría usar Integer.parseInt(newValue)y usar tryy catchdetectar un error enNumberFormatException
Pixel

43

Actualización de abril de 2016

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

Desde Java 8u40, Java tiene un TextFormatter que generalmente es mejor para hacer cumplir la entrada de formatos específicos, como números en JavaFX TextFields:

Vea 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 ejemplos a continuación:

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

42

Sé que este es un hilo bastante antiguo, pero para los futuros lectores, aquí hay otra solución que encontré 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 las mejoras sugeridas.


1
¿Alguien sabe qué expresión regular usar para números decimales (por ejemplo, 123.456)? El valor del parámetro de función de validación (texto) "texto" es el último carácter editado por el usuario, no la cadena completa en el campo de texto, por lo que la expresión regular puede no coincidir correctamente. Por ejemplo, estoy usando este comando así: text.matches("\\d+"); y no puedo eliminar ningún carácter en el campo de texto
mils

1
@mils Mira aquí y aquí .
vellotis

1
Creo que esta es una buena solución, pero cambiaría una cosa. En lugar de escribir text.matches ("[0-9] *"), crearía una variable para el patrón y la compilaría. En su código, el patrón se compila cada vez que alguien escribe un carácter en un campo, y esto es costoso para la CPU y la memoria. Entonces agregaría variable: patrón estático privado integerPattern = Pattern.compile ("[0-9] *"); Y reemplace el cuerpo de validación con: integerPattern.matcher (text) .matches ();
P. Jowko

38

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

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

Esto evita tanto la subclasificación como los eventos de cambio duplicados que obtendrá cuando agregue un detector de cambios a la propiedad del texto y modifique el texto en ese detector.


30

El TextInputtiene una TextFormatterque puede ser usado para formatear, convertir y limitar los tipos de texto que se puede introducir.

El TextFormattertiene un filtro que puede ser utilizado para rechazar de entrada. Necesitamos configurar esto para rechazar cualquier cosa que no sea un entero válido. También tiene un convertidor que debemos configurar para convertir el valor de la cadena en un valor entero que podemos vincular más adelante.

Creemos un filtro reutilizable:

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

El filtro puede hacer una de estas tres cosas, puede devolver el cambio sin modificar para aceptarlo tal como está, puede alterar el cambio de la forma que considere adecuada o puede volver nulla rechazar el cambio por completo.

Usaremos el estándar IntegerStringConvertercomo convertidor.

Poniéndolo todo junto tenemos:

TextField textField = ...;

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

textField.setTextFormatter(formatter);

Si no necesita un filtro reutilizable, puede hacer este elegante de una sola línea en su lugar:

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

21

No me gustan las excepciones, así que usé la matchesfunción de 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);
        }
    }
});

1
a veces tiene una excepción, porque el nuber podría ser demasiado grande.
schlagi123

1
Consejo: sustituya la expresión regular "\\ d +" por "\\ d *". Esto le permitirá eliminar todos los caracteres del campo de entrada de texto (use la tecla de retroceso / borrar para borrar el campo).
Guus

1
No se olvide de establecer la posición de intercalación después de poner de nuevo al valor anterior:textField.positionCaret(textField.getLength());
hsson

Cambie la primera condición si a: if (newValue.matches("\\d*") && newValue.getText().length < 5) si desea limitar la entrada a 4 dígitos en este caso.
Cappuccino90

@ Cappuccino90 Debe cambiar las declaraciones porque la verificación de longitud es mucho más barata en cada hit que analizar una expresión regular
eso es

9

A partir de Java SE 8u40 , para tal necesidad puede utilizar un " entero "Spinner permite seleccionar de forma segura un entero válido mediante las teclas de flecha hacia arriba / flecha hacia abajo del teclado o los botones de flecha hacia arriba / flecha hacia abajo provistos.

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

Por ejemplo

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

Ejemplo de resultado con una ruleta " entera " y una ruleta " doble "

ingrese la descripción de la imagen aquí

Una ruleta 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 de una secuencia ordenada de dichos valores. Los giradores suelen proporcionar un par de botones de flecha diminutos para recorrer los elementos de la secuencia. Las teclas de flecha hacia arriba / flecha hacia abajo del teclado también recorren los elementos. También se le puede permitir al usuario escribir un valor (legal) directamente en la ruleta. Aunque los cuadros combinados brindan una funcionalidad similar, a veces se prefieren los indicadores giratorios porque no requieren una lista desplegable que pueda ocultar datos importantes, y también porque permiten características como ajustar desde el valor máximo hasta el valor mínimo (por ejemplo, desde el entero positivo más grande hasta 0).

Más detalles sobre el control Spinner


La gente no debería, el spinner no valida la entrada de texto o pierde el enfoque
geometrikal

7

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

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

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

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


9
Woot? ¿Desde cuándo se analiza 0.5d correctamente por Integer.parseInt ()? Lanza una java.lang.NumberFormatException: para la cadena de entrada: "0.5d" (como se esperaba, ya que no es un entero)
Burkhard

4

Pruebe este sencillo código y 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;
    }
}));

3

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

txtMinPrice.textProperty().addListener(forceNumberListener);
txtMaxPrice.textProperty().addListener(forceNumberListener);

3

Este funcionó para mí.

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

3

Quiero ayudar con mi idea de combinar la respuesta de Evan Knowles con TextFormatterJavaFX 8

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

así que buena suerte;) mantén la calma y codifica java


2

Aquí hay una clase simple que maneja algunas validaciones básicas TextField, usando TextFormatterintroducido 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<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);
    }

}

Puedes usarlo así:

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

o puede instanciarlo en un archivo fxml y aplicarlo a un customTextField con las propiedades correspondientes.

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

Código en github


2

Esto es lo que uso:

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

    }
});

Lo mismo en notación lambda sería:

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

2

Este método permite a TextField finalizar todo el procesamiento (copiar / pegar / deshacer de forma segura). No requiere extender las clases y le permite decidir qué hacer con el texto nuevo 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, simplemente 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);
   }

0

Mmmm. Me encontré con ese problema hace semanas. Como la API no proporciona un control para lograrlo,
es posible que desee utilizar uno 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 una normal TextBox,
pero también tiene un valueatributo que almacena el entero ingresado.
Cuando el control pierde el foco, valida el valor y lo revierte (si no es válido).


2
¿Qué lenguaje es este?
Stealth Rabbi

Es JavaFX Script Rabbi, está obsoleto .
jewelsea

0

este Código Haga su campo de texto Acepte solo 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));
            }
        }
    });

0

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

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

    }
});

-1

En las actualizaciones recientes de JavaFX, debe establecer un nuevo texto en el método Platform.runLater así:

    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.


1
¡Gracias por su respuesta! ¿Podría explicarnos un poco por qué Platform.runLaterse necesita?
TuringTux

@TuringTux en la versión reciente de JavaFX, arrojará una excepción si no usa Platform.runLater
bdshahab

-1

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 del componente UI. Inicialización:

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

Y el método numbericSanitization:

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

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

Sincronización de palabras claveSe agrega la para evitar un posible problema de bloqueo de renderizado en javafx si se llamará a setText antes de que la antigua finalice la ejecución. Es fácil de reproducir si comienza a escribir caracteres incorrectos muy rápido.

Otra ventaja es que solo mantiene un patrón para que coincida y solo realiza la reversión. Es mejor porque puede abstraer fácilmente la solución para diferentes patrones de desinfección.


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

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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.