Qual è il modo consigliato per creare un campo di testo numerico in JavaFX?


85

Devo limitare l'input in un TextField a numeri interi. Qualche consiglio?


3
Nota: ascoltando il testo le proprietà sono sbagliate ! Questo era il meglio che si potesse fare prima di avere un TextFormatter (da fx8u40 o giù di lì). Da allora, si applica la regola di base: in generale è considerata una cattiva pratica modificare il valore osservato in questo metodo e l'utilizzo di un TextFormatter è l' unica soluzione accettabile.
kleopatra

Risposte:


120

Thread molto vecchio, ma questo sembra più ordinato e rimuove i caratteri non numerici se incollati.

// 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
Funziona meravigliosamente. Nota che puoi anche usare \\D+(o solo \\D) al posto di [^\\d], se vuoi salvare alcuni caratteri.
ricky3350

5
Ancora più semplice è reimpostare il testo su oldValue. In questo modo non devi scherzare con le sostituzioni di regex (che sfortunatamente non ha funzionato per me).
geisterfurz007

@ geisterfurz007 Questo offre la possibilità di rimuovere i non numerici dal testo incollato.
Evan Knowles

10
Non aggiornato, vedere la risposta di TextFormatter di seguito.
gerardw

3
Potrebbe anche essere sufficiente utilizzare Integer.parseInt(newValue)e utilizzare trye catchper individuare un errore suNumberFormatException
Pixel

44

Aggiornamento aprile 2016

Questa risposta è stata creata alcuni anni fa e la risposta originale è in gran parte obsoleta ora.

A partire da Java 8u40, Java ha un TextFormatter che di solito è il migliore per imporre l'input di formati specifici come i valori numerici su JavaFX TextFields:

Vedi anche altre risposte a questa domanda che menzionano specificamente TextFormatter.


Risposta originale

Ci sono alcuni esempi di questo in questo gist , ho duplicato uno degli esempi seguenti:

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

43

So che questo è un thread piuttosto vecchio, ma per i futuri lettori ecco un'altra soluzione che ho trovato abbastanza 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]*");
    }
}

Modifica: grazie none_ e SCBoy per i miglioramenti suggeriti.


1
Qualcuno sa quale regex usare per i numeri decimali (ad esempio 123.456)? Il valore del parametro della funzione di convalida (testo) "testo" è l'ultimo carattere modificato dall'utente, non l'intera stringa nel campo di testo, pertanto la regex potrebbe non corrispondere correttamente. Ad esempio, sto usando questo comando in questo modo: text.matches("\\d+"); e non posso eliminare alcun carattere nel campo di testo
mils

1
@mils Guarda qui e qui .
vellotis

1
Penso che questa sia una buona soluzione, ma cambierei una cosa. Invece di scrivere text.matches ("[0-9] *") creerei la variabile per pattern e la compilerei. Nel tuo codice il pattern viene compilato ogni volta che qualcuno scrive caratteri in un campo, e questo è costoso per CPU e memoria. Quindi aggiungerei la variabile: private static Pattern integerPattern = Pattern.compile ("[0-9] *"); E sostituire validate body con: integerPattern.matcher (text) .matches ();
P. Jowko

38

A partire da JavaFX 8u40, puoi impostare un oggetto TextFormatter su un campo di testo:

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

Questo evita sia la sottoclasse che gli eventi di modifica duplicati che otterrai quando aggiungi un listener di modifiche alla proprietà text e modifichi il testo in quel listener.


30

Il TextInputha un TextFormatterche può essere utilizzato per formattare, convertire e limitare i tipi di testo che possono essere inseriti.

Il TextFormatterha un filtro che può essere utilizzata per rifiutare ingresso. Dobbiamo impostarlo per rifiutare tutto ciò che non è un numero intero valido. Ha anche un convertitore che dobbiamo impostare per convertire il valore della stringa in un valore intero che possiamo associare in seguito.

Consente di creare un filtro riutilizzabile:

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

Il filtro può fare una di queste tre cose, può restituire la modifica non modificata per accettarla così com'è, può alterare la modifica in qualche modo che ritiene opportuno o può tornare nulla rifiutare la modifica tutti insieme.

Useremo lo standard IntegerStringConvertercome convertitore.

Mettendo tutto insieme abbiamo:

TextField textField = ...;

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

textField.setTextFormatter(formatter);

Se vuoi non aver bisogno di un filtro riutilizzabile, puoi fare questo fantastico one-liner:

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

21

Non mi piacciono le eccezioni, quindi ho usato la matchesfunzione di 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 volte hai un'eccezione, perché il nuber potrebbe essere troppo grande.
schlagi123

1
Suggerimento: sostituisci la regex "\\ d +" con "\\ d *". Ciò ti consentirà di rimuovere tutti i caratteri dal campo di immissione del testo (usa backspace / cancella per cancellare il campo).
Guus

1
Non dimenticare di impostare la posizione del cursore dopo averla riportata al vecchio valore:textField.positionCaret(textField.getLength());
hsson

Modificare la condizione del pugno se in: if (newValue.matches("\\d*") && newValue.getText().length < 5) se si desidera limitare l'immissione a 4 cifre in questo caso.
Cappuccino90

@ Cappuccino90 Dovresti cambiare le istruzioni perché il controllo della lunghezza è molto più economico su ogni hit rispetto
all'analisi di un'espressione regolare

9

A partire da Java SE 8u40 , per tale esigenza è possibile utilizzare un " intero " che Spinnerconsente di selezionare in sicurezza un intero valido utilizzando i tasti freccia su / giù della tastiera oi pulsanti freccia su / freccia giù forniti.

È inoltre possibile definire un valore minimo , massimo e iniziale per limitare i valori consentiti e un importo da aumentare o diminuire di, per passo.

Per esempio

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

Esempio di risultato con uno spinner " intero " e uno spinner " doppio "

inserisci qui la descrizione dell'immagine

Uno spinner è un controllo di campo di testo a riga singola che consente all'utente di selezionare un numero o un valore oggetto da una sequenza ordinata di tali valori. Gli spinner in genere forniscono un paio di piccoli pulsanti freccia per scorrere gli elementi della sequenza. Anche i tasti freccia su / freccia giù della tastiera scorrono gli elementi. L'utente può anche essere autorizzato a digitare un valore (legale) direttamente nello spinner. Sebbene le caselle combinate forniscano funzionalità simili, a volte sono preferiti i filatori perché non richiedono un elenco a discesa che può oscurare dati importanti e anche perché consentono funzionalità come il ritorno a capo dal valore massimo al valore minimo (ad esempio, dal numero intero positivo più grande a 0).

Maggiori dettagli sul controllo Spinner


Le persone non dovrebbero che lo spinner non convalidi l'immissione di testo o ha perso il focus
geometrico

7

La risposta preferita può essere ancora più piccola se si utilizza 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 ifclausola è importante per gestire input come 0.5d o 0.7f che sono correttamente analizzati da Int.parseInt (), ma non dovrebbero apparire nel campo di testo.


9
Woot? Da quando 0.5d viene analizzato correttamente da Integer.parseInt () ?! Genera un'eccezione java.lang.NumberFormatException: per la stringa di input: "0.5d" (come previsto, poiché non è un numero intero)
Burkhard

4

Prova questo semplice codice che farà il lavoro.

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

Se vuoi applicare lo stesso listener a più di un TextField, ecco la soluzione più semplice:

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

Questo ha funzionato per 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);
            }
        }
    });
}

3

Voglio aiutare con la mia idea combinando la risposta di Evan Knowles con TextFormatterJavaFX 8

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

quindi buona fortuna;) mantieni la calma e codifica java


2

Ecco una semplice classe che gestisce alcune convalide di base TextField, utilizzando TextFormatterintrodotto in JavaFX 8u40

MODIFICARE:

(Codice aggiunto riguardo al commento di 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);
    }

}

Puoi usarlo in questo modo:

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

oppure puoi istanziarlo in un file fxml e applicarlo a un customTextField con le proprietà corrispondenti.

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

Codice su GitHub


2

Questo è quello che 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 stesso nella notazione lambda sarebbe:

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

2

Questo metodo consente a TextField di completare tutta l'elaborazione (copia / incolla / annulla in modo sicuro). Non richiede di estendere le classi e consente di decidere cosa fare con il nuovo testo dopo ogni modifica (per spingerlo alla logica, o tornare al valore precedente, o anche per 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());
  }
);

Per il tuo caso, aggiungi questa logica all'interno. Funziona perfettamente.

   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. Mi sono imbattuto in quel problema settimane fa. Poiché l'API non fornisce un controllo per ottenere ciò,
potresti voler utilizzare il tuo.
Ho usato qualcosa come:

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

Viene utilizzato allo stesso modo di un normale TextBox,
ma ha anche un valueattributo che memorizza il numero intero inserito.
Quando il controllo perde il focus, convalida il valore e lo ripristina (se non è valido).


2
che lingua è questa?
Stealth Rabbi

È JavaFX Script Rabbi, è obsoleto .
jewelsea

0

questo codice Rendi il tuo campo di testo accettare solo il numero

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

Questo codice funziona bene per me anche se provi a copiare / incollare.

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

    }
});

-1

Negli ultimi aggiornamenti di JavaFX, devi impostare un nuovo testo nel metodo Platform.runLater proprio in questo modo:

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

È una buona idea impostare anche la posizione del cursore.


1
La ringrazio per la risposta! Potresti spiegare un po 'perché Platform.runLaterè necessario?
TuringTux

@TuringTux nella versione recente di JavaFX, genererà un'eccezione se non si utilizza Platform.runLater
bdshahab

-1

Vorrei migliorare la risposta di Evan Knowles: https://stackoverflow.com/a/30796829/2628125

Nel mio caso ho avuto lezioni con gestori per la parte del componente dell'interfaccia utente. Inizializzazione:

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

E il metodo 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);
    }
}

Parola chiave sincronizzata viene aggiunta per prevenire possibili problemi di blocco del rendering in javafx se setText verrà chiamato prima che il vecchio sia terminato. È facile da riprodurre se inizi a digitare caratteri sbagliati molto velocemente.

Un altro vantaggio è che mantieni un solo modello da abbinare e fai semplicemente il rollback. È meglio perché puoi facilmente astragare la soluzione per diversi modelli di sanificazione.


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

Funziona bene in quanto consente di inserire solo un valore intero e un valore decimale (con codice ASCII 46).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.