web-dev-qa-db-fra.com

Quelle est la méthode recommandée pour créer un TextField numérique en JavaFX?

Je dois limiter l'entrée dans un champ de texte aux entiers. Aucun conseil?

72
Harry Mitchell

Très vieux fil, mais cela semble plus ordonné et supprime les caractères non numériques s'il est collé.

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

Je sais que c'est un fil assez ancien, mais pour les futurs lecteurs, voici une autre solution que j'ai trouvée assez 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: Merci none_ et SCBoy pour les améliorations suggérées.

37
Burkhard

Mise à jour avril 2016

Cette réponse a été créée il y a quelques années et la réponse d'origine est en grande partie obsolète maintenant. 

Depuis Java 8u40, Java a un TextFormatter _ qui convient généralement mieux pour imposer la saisie de formats spécifiques tels que les valeurs numériques sur JavaFX TextFields:

Voir aussi les autres réponses à cette question qui mentionnent spécifiquement TextFormatter.


Réponse originale

Il y a quelques exemples de cela dans ce Gist , j'ai dupliqué l'un des exemples ci-dessous:

// 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 (!"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)) {
          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()));
      }
    });
  }
}
31
jewelsea

La TextInput a une TextFormatter qui peut être utilisée pour formater, convertir et limiter les types de texte pouvant être saisis. 

La TextFormatter a un filtre qui peut être utilisé pour rejeter une entrée. Nous devons définir ceci pour rejeter tout ce qui n'est pas un entier valide. Il possède également un convertisseur que nous devons définir pour convertir la valeur de chaîne en valeur entière que nous pourrons lier ultérieurement.

Permet de créer un filtre réutilisable:

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

Le filtre peut effectuer l'une des trois opérations suivantes: il peut renvoyer le changement sans modification et l'accepter tel quel, il peut modifier le changement de la manière qu'il juge appropriée ou renvoyer null pour le refuser complètement.

Nous utiliserons le standard IntegerStringConverter en tant que convertisseur. 

En réunissant tout cela, nous avons:

TextField textField = ...;

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

textField.setTextFormatter(formatter);

Si vous souhaitez ne pas avoir besoin d'un filtre réutilisable, vous pouvez utiliser cette option à la place:

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

À partir de JavaFX 8u40, vous pouvez définir un objet TextFormatter sur un champ de texte:

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

Cela évite les événements de modification de sous-classe et de duplication que vous obtiendrez lorsque vous ajoutez un écouteur de modification à la propriété text et que vous modifiez le texte dans cet écouteur.

22
Uwe

Je n'aime pas les exceptions et j'ai donc utilisé la fonction matches 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);
        }
    }
});
21
thatsIch
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 clause if est importante pour gérer des entrées telles que 0.5d ou 0.7f qui sont correctement analysées par Int.parseInt (), mais ne doivent pas apparaître dans le champ de texte.

6
ubuntudroid

À partir de Java SE 8u40, vous pouvez utiliser un "entier" Spinner pour sélectionner en toute sécurité un entier valide à l’aide des touches fléchées haut/bas du clavier ou flèches haut/bas fournies. 

Vous pouvez également définir une valeur min, max et initiale pour limiter les valeurs autorisées et un montant à incrémenter ou à diminuer.

Par exemple

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

Exemple de résultat avec un spinner "entier _" et un spinner "double"

 enter image description here

Un spinner est un contrôle de champ de texte d'une seule ligne qui permet à l'utilisateur sélectionner un nombre ou une valeur d'objet dans une séquence ordonnée de ce type valeurs. Les filateurs fournissent généralement une paire de minuscules boutons fléchés pour parcourir les éléments de la séquence. Le clavier est en place Les touches fléchées/bas permettent également de parcourir les éléments. L'utilisateur peut également être autorisé à taper une valeur (légale) directement dans la roulette . Bien que les listes déroulantes offrent des fonctionnalités similaires, les centrifugeuses sont parfois préféré car ils ne nécessitent pas une liste déroulante que peut masquer des données importantes, et aussi parce qu’elles permettent des fonctionnalités tels que le retour à la ligne de la valeur maximale à la valeur minimale (par exemple, du plus grand entier positif à 0).

Plus de détails sur le contrôle Spinner

6
Nicolas Filotto

La réponse préférée peut être encore plus petite si vous utilisez Java 1.8 Lambdas

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

Celui-ci a fonctionné pour moi.

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
Martin

Si vous souhaitez appliquer le même écouteur à plusieurs TextField, voici la solution la plus 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);
2
javasuns

Essayez ce code simple, il fera le travail.

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;
    }
}));
2
Hassan Latif

Cette méthode permet à TextField de terminer tous les traitements (copier/coller/annuler sûr) . Ne nécessite pas d’extension de classes et vous permet de décider quoi faire du nouveau texte après chaque modification ou revenir à la valeur précédente, ou même la modifier).

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

Pour votre cas, ajoutez simplement cette logique à l'intérieur. Marche parfaitement.

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

C'est ce que j'utilise:

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

    }
});

La même chose en notation lambda serait:

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

Voici une classe simple qui gère certaines validations de base sur TextField, en utilisant TextFormatter introduit dans JavaFX 8u40

MODIFIER: 

(Code ajouté concernant le commentaire 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);
    }

}

Vous pouvez l'utiliser comme ceci:

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

ou vous pouvez l'instancier dans un fichier xml et l'appliquer à un customTextField avec les propriétés correspondantes.

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 sur github

1
jns

Ce code fonctionne bien pour moi même si vous essayez de copier/coller.

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

    }
});
1
Ahmed Jaouadi

je veux aider avec mon idée de combiner Evan Knowles répondre au format texte de Javafx 8 

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

alors bonne chance;) garde ton calme et code Java

1
Salah Eddine Chaibi

Dans les dernières mises à jour de JavaFX, vous devez définir un nouveau texte dans la méthode Platform.runLater comme suit:

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

C'est aussi une bonne idée de définir la position du curseur.

0
bdshahab

ce code Faites votre textField Accepter seulement le nombre 

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

Cela fonctionne bien car cela vous permet d’entrer uniquement la valeur entière et la valeur décimale (code ASCII 46).

0
ShubhamWanne

J'aimerais améliorer la réponse de Evan Knowles: https://stackoverflow.com/a/30796829/2628125

Dans mon cas, j'ai eu cours avec des gestionnaires pour la partie composant d'interface utilisateur. Initialisation:

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

Et la méthode 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);
    }
}

Le mot clé synchronized est ajouté pour éviter un problème de verrouillage de rendu dans javafx si setText est appelé avant l'exécution de l'ancien. Il est facile à reproduire si vous commencez à taper très vite les mauvais caractères.

Un autre avantage est que vous ne conservez qu'un seul motif et que vous effectuez une restauration. C'est mieux, car vous pouvez facilement faire abstraction de la solution pour différents modèles d'assainissement.

0
Oleksandr Knyga

Mmmm. J'ai rencontré ce problème il y a des semaines. Comme l’API ne fournit pas de contrôle pour y parvenir,
vous voudrez peut-être utiliser le vôtre.
J'ai utilisé quelque chose comme:

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

Il est utilisé de la même manière qu'un TextBox normal,
mais a aussi un attribut value qui stocke l’entier saisi.
Lorsque le contrôle perd le focus, il valide la valeur et la restaure (si non valide).

0
Alba Mendez