it-swarm.com.de

Was ist der empfohlene Weg, um ein numerisches TextField in JavaFX zu erstellen?

Ich muss die Eingabe in ein TextField auf ganze Zahlen beschränken. Irgendein Rat?

72
Harry Mitchell

Sehr alter Thread, aber dies scheint ordentlicher zu sein und entfernt nicht numerische Zeichen, wenn er eingefügt wird.

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

Ich weiß, dass dies ein ziemlich alter Thread ist, aber für zukünftige Leser ist eine andere Lösung, die ich ziemlich intuitiv fand:

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: Danke none_ und SCBoy für deine Verbesserungsvorschläge.

37
Burkhard

Update April 2016

Diese Antwort wurde vor einigen Jahren erstellt und die ursprüngliche Antwort ist jetzt weitgehend veraltet. 

Seit Java 8u40 verfügt Java über ein TextFormatter , das normalerweise am besten geeignet ist, um die Eingabe bestimmter Formate wie z.

Siehe auch andere Antworten auf diese Frage, in denen TextFormatter ausdrücklich erwähnt wird.


Originalantwort

Es gibt einige Beispiele in dieser Gist , ich habe eines der folgenden Beispiele dupliziert:

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

Die Variable TextInput hat eine Variable TextFormatter, mit der die eingegebenen Texttypen formatiert, konvertiert und begrenzt werden können. 

Die Variable TextFormatter verfügt über einen Filter, mit dem Eingaben abgelehnt werden können. Wir müssen dies festlegen, um alles abzulehnen, das keine gültige Ganzzahl ist. Es hat auch einen Konverter, den wir setzen müssen, um den String-Wert in einen ganzzahligen Wert umzuwandeln, den wir später binden können.

Erstellen Sie einen wiederverwendbaren Filter:

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

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

Der Filter kann eines von drei Dingen ausführen, er kann die Änderung unverändert zurückgeben, um sie so zu akzeptieren, wie sie ist, sie kann die Änderung auf eine Art ändern, die sie für passend hält, oder sie kann null zurückgeben, um die Änderung insgesamt abzulehnen.

Wir verwenden den Standard IntegerStringConverter als Konverter. 

Alles zusammen haben wir:

TextField textField = ...;

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

textField.setTextFormatter(formatter);

Wenn Sie keinen wiederverwendbaren Filter benötigen, können Sie stattdessen diesen fantastischen Einzeiler verwenden:

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

Beginnend mit JavaFX 8u40 können Sie ein TextFormatter-Objekt in einem Textfeld festlegen:

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

Dadurch werden sowohl Unterklassen als auch doppelte Änderungsereignisse vermieden, die Sie erhalten, wenn Sie der Texteigenschaft einen Änderungslistener hinzufügen und den Text in diesem Listener ändern.

22
Uwe

Ich mag keine Ausnahmen, deshalb habe ich die matches-Funktion von String-Class verwendet

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

Die if-Klausel ist wichtig, um Eingaben wie 0.5d oder 0.7f zu behandeln, die von Int.parseInt () korrekt analysiert werden, aber nicht im Textfeld erscheinen sollten.

6
ubuntudroid

Ausgehend von Java SE 8u40 können Sie für eine solche Anforderung ein "Ganzzahl" Spinner verwenden, mit dem Sie eine gültige Ganzzahl sicher auswählen können, indem Sie die Aufwärts-/Abwärtspfeiltasten der Tastatur oder die Pfeiltasten verwenden Aufwärtspfeil/Abwärtspfeil vorgesehene Tasten. 

Sie können auch einen min -, einen max - und einen initial - Wert definieren, um die zulässigen Werte und einen Betrag zu begrenzen, um den Sie pro Schritt erhöhen oder verringern können.

Zum Beispiel

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

Beispiel für ein Ergebnis mit einem Spinner "Ganzzahl" und einem Spinner "double"

 enter image description here

Ein Spinner ist ein einzeiliges Textfeldsteuerelement, mit dem der Benutzer Wählen Sie eine Zahl oder einen Objektwert aus einer geordneten Reihenfolge aus Werte. Spinner stellen normalerweise ein Paar winziger Pfeiltasten für .__ bereit. Schritt durch die Elemente der Sequenz. Die Tastatur ist auf Pfeil-/Abwärtspfeiltasten durchlaufen auch die Elemente. Der Benutzer kann Sie können auch einen (legalen) Wert direkt in den Spinner eingeben. Kombinationsfelder bieten zwar ähnliche Funktionen, Spinner sind jedoch manchmal bevorzugt, weil sie keine Dropdown-Liste benötigen, die kann wichtige Daten verschleiern, und auch, weil sie Funktionen zulassen B. vom maximalen Wert zurück zum minimalen Wert (z. B. von der größten positiven ganzen Zahl bis 0).

Weitere Details zu der Spinner-Steuerung

6
Nicolas Filotto

Die bevorzugte Antwort kann noch geringer sein, wenn Sie Java 1.8-Lambdas verwenden

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

Dieser hat für mich gearbeitet.

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

Wenn Sie denselben Listener auf mehrere TextFields anwenden möchten, finden Sie hier die einfachste Lösung:

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

Versuchen Sie es mit diesem einfachen Code.

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

Mit dieser Methode kann TextField die gesamte Verarbeitung abschließen (copy/paste/rückgängig machen) . Die Klassen müssen nicht erweitert werden, und Sie können nach jeder Änderung entscheiden, was mit neuem Text geschehen soll oder zum vorherigen Wert zurückkehren oder ihn sogar ändern).

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

Für Ihren Fall fügen Sie einfach diese Logik hinzu. Funktioniert perfekt.

   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

Das ist was ich benutze:

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

    }
});

Das gleiche in der Lambda-Notation wäre:

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

Hier ist eine einfache Klasse, die einige grundlegende Validierungen für TextField verarbeitet, wobei TextFormatter in JavaFX 8u40 eingeführt wurde

EDIT: 

(Code zum Kommentar von Floern hinzugefügt)

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

}

Sie können es so verwenden:

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

oder Sie können es in einer fxml-Datei instanziieren und auf ein customTextField mit den entsprechenden Eigenschaften anwenden.

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

1
jns

Dieser Code funktioniert gut für mich, auch wenn Sie versuchen, zu kopieren/einfügen.

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

    }
});
1
Ahmed Jaouadi

ich möchte mit meiner Idee helfen, Evan Knowles Antwort mit dem Textformat von Javafx 8 zu kombinieren 

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

so viel glück;) bleib ruhig und programmiere Java

1

In den letzten Aktualisierungen von JavaFX müssen Sie den neuen Text in der Platform.runLater-Methode folgendermaßen festlegen:

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

Es ist eine gute Idee, auch die Caret-Position festzulegen.

0
bdshahab

dieser Code Lass deinen TextField nur die Zahl annehmen 

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

Dies funktioniert gut, da Sie nur den ganzzahligen Wert und den Dezimalwert (mit dem ASCII - Code 46) eingeben können.

0
ShubhamWanne

Ich möchte die Antwort von Evan Knowles verbessern: https://stackoverflow.com/a/30796829/2628125

In meinem Fall hatte ich eine Klasse mit Handlern für UI-Komponententeil. Initialisierung:

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

Und die numbericSanitization-Methode:

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

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

Das Schlüsselwort synchronized wurde hinzugefügt, um ein mögliches Problem bei der Render-Sperre in javafx zu verhindern, wenn setText aufgerufen wird, bevor der alte beendet ist. Es ist einfach zu reproduzieren, wenn Sie sehr schnell falsche Zeichen eingeben.

Ein weiterer Vorteil besteht darin, dass Sie nur ein einziges Muster beibehalten und einfach ein Rollback ausführen. Es ist besser, weil Sie die Lösung für unterschiedliche Desinfektionsmuster leicht abschätzen können.

0
Oleksandr Knyga

Mmmm. Ich bin vor Wochen auf dieses Problem gestoßen. Da die API keine Kontrolle bietet, um dies zu erreichen,
Sie möchten vielleicht Ihre eigene verwenden.
Ich habe etwas verwendet wie:

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

Es wird auf die gleiche Weise verwendet wie ein normales TextBox,
hat aber auch ein value-Attribut, das die eingegebene Ganzzahl speichert.
Wenn das Steuerelement den Fokus verliert, wird der Wert validiert und der Wert wird zurückgesetzt (falls nicht gültig).

0
Alba Mendez