在JavaFX中build立数字TextField的推荐方法是什么?

我需要限制input到一个TextField整数。 任何build议?

非常古老的线程,但这看起来更整洁,并删除非数字字符,如果粘贴。

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

我知道这是一个相当古老的线程,但对于未来的读者来说,这是另一个我觉得非常直观的解决scheme:

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

编辑:谢谢none_和SCBoy你的build议改进。

2016年4月更新

这个答案是几年前创build的,原来的答案在很大程度上已经过时了。

从Java 8u40开始,Java有一个TextFormatter ,它通常最适合在JavaFX TextField上强制执行特定格式(如数字)的input:

  • Java 8 U40 TextFormatter(JavaFX)仅限于十进制数的用户input
  • string与数字和字母加倍javafx

另请参阅其他特别提到TextFormatter的问题的答案。


原始答复

在这个要点中有一些例子,我重复了下面的例子之一:

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

我不喜欢exception,因此我使用了String-Class中的matches函数

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

TextInput有一个TextFormatter ,可以用来格式化,转换和限制可以input的文本的types。

TextFormatter有一个可以用来拒绝input的filter。 我们需要设置这个来拒绝任何不是有效整数的东西。 它也有一个转换器,我们需要设置将string值转换为一个整数值,我们可以稍后进行绑定。

让我们创build一个可重用的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; } } 

filter可以做三件事情之一,它可以返回未经修改的变化来接受它,它可以以某种认为合适的方式改变变化,或者可以返回null来拒绝变化。

我们将使用标准的IntegerStringConverter作为转换器。

把它放在一起,我们有:

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

如果你想不需要一个可重用的filter,你可以做这个花哨的一行,而不是:

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

从JavaFX 8u40开始,您可以在文本字段上设置一个TextFormatter对象:

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

这可以避免将更改侦听器添加到文本属性并修改该侦听器中的文本时获得的子类和重复更改事件。

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

if子句对处理类似0.5d或0.7f的input非常重要,这些input可以通过Int.parseInt()正确parsing,但不应出现在文本字段中。

这是我使用的:

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

在lambda表示法中相同的是:

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

如果您想将同一个侦听器应用于多个TextField,则这是最简单的解决scheme:

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

试试这个简单的代码,它将完成这项工作。

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

这个方法让TextField完成所有处理(复制/粘贴/撤消安全)。 不需要扩展类,并允许您决定在每次更改之后如何处理新文本(将其推送到逻辑或返回到之前的值,甚至修改它)。

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

为了你的情况,只需添加这个逻辑里面。 完美的作品。

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

如果您使用Java 1.8 Lambdas,则优先回答可能更小

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

Java SE 8u40开始 ,为了满足这种需求,您可以使用“ 整数Spinner ,通过使用键盘的向上箭头/向下箭头键或向上箭头/向下箭头提供的button来安全地select有效整数。

您还可以定义一个最小值 ,一个最大值和一个初始值,以限制允许的值以及每步增加或减less的数量。

例如

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

整数 ”微调器和“ ”微调器的结果示例

在这里输入图像描述

微调器是一种单行文本字段控件,可让用户从这些值的有序序列中select数字或对象值。 纺纱厂通常会提供一对细小的箭头button,用于逐步排列序列中的元素。 键盘的向上/向下箭头键也循环通过元素。 用户也可以被允许直接input一个(合法的)值到微调器中。 尽pipecombobox提供了类似的function,但是有时候,首选项是首选的,因为它们不需要可以遮蔽重要数据的下拉列表,还因为它们允许从最大值回到最小值的function(例如,从最大的正整数到0)。

有关微调控件的更多细节

这个为我工作。

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

这是一个简单的类,它使用JavaFX 8u40中引入的TextFormatter处理TextField上的一些基本validation

编辑:

(添加关于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); } } 

你可以像这样使用它:

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

或者你可以在一个fxml文件中实例化它,并将其应用到具有相应属性的customTextField。

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

在github上的代码

这个代码让你的textField只接受数字

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

嗯。 几周前我遇到过这个问题。 由于API不提供控制来实现这一点,
你可能想用你自己的。 我用了这样的东西:

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

它的使用方式与普通的TextBox相同,
但也有一个存储input整数的value属性。
当控制失去焦点时,它将validation该值并将其还原(如果无效)。

在JavaFX的最新更新中,您必须像这样在Platform.runLater方法中设置新文本:

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

设置插入位置也是一个好主意。

我想提高Evan Knowles的答案: https : //stackoverflow.com/a/30796829/2628125

在我的情况下,我有与UI组件部分的处理程序类。 初始化:

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

而数字化的方法:

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

添加关键字synchronized以防止在javafx中可能的render lock问题,如果setText将在旧执行完成之前被调用。 如果你真的开始input错误的字符,很容易重现。

另一个好处是,你只保留一个模式匹配,只是做回滚。 最好是因为你可以很容易地为不同的消毒模式提供解决scheme。

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

这工作正常,因为它只允许你input整数值和十进制值(具有ASCII码46)。