在JavaFX中创建数字TextField的推荐方法是什么?


85

我需要将TextField的输入限制为整数。有什么建议吗?


3
注意:听textProperty是错误的!那是在拥有TextFormatter之前可以做的最好的事情(大约从fx8u40开始)。从那时起,就应用了基本规则:通常,在这种方法中修改观察值被认为是不好的做法,并且使用TextFormatter唯一可接受的解决方案。
kleopatra

Answers:


118

线程很旧,但是看起来更整洁,并且如果粘贴则去除非数字字符。

// 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
效果很好。请注意,如果要保存一些字符,也可以使用\\D+(或仅使用\\D)代替[^\\d]
ricky3350

5
更简单的是将文本设置回oldValue。这样,您就不必搞砸正则表达式替换了(不幸的是,这对我来说根本不起作用)。
geisterfurz007

@ geisterfurz007这确实提供了从粘贴的文本中剥离非数字的选项。
伊万·诺尔斯

10
过时的,请参见下面的TextFormatter答案。
gerardw '18

3
也可能只是使用Integer.parseInt(newValue)和利用try以及catch对发现错误NumberFormatException
像素

43

2016年4月更新

这个答案是几年前创建的,而现在原来的答案已经过时了。

从Java 8u40开始,Java具有TextFormatter,通常最适合用于强制执行特定格式的输入,例如JavaFX TextField上的数字:

另请参见对此问题的其他答案,其中特别提到了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(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()));
      }
    });
  }
}

41

我知道这是一个比较老的话题,但是对于以后的读者来说,这是我发现的非常直观的另一种解决方案:

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您提出的改进建议。


1
有谁知道十进制数字使用什么正则表达式(例如123.456)?validate(text)函数参数“ text”的值是最后一个用户编辑的字符,而不是text字段中的整个字符串,因此regex可能不正确匹配。例如,我正在这样使用该命令: text.matches("\\d+"); 并且我无法删除文本字段中的任何字符
mils

1
@mils在这里这里看看。
vellotis '16

1
我认为这是一个很好的解决方案,但是我会改变一件事。而不是编写text.matches(“ [0-9] *”),我将为pattern创建变量并将其编译。每当有人在一个字段中写入字符时,在您的代码模式中都会对其进行编译,这对于CPU和内存来说是很昂贵的。因此,我将添加变量:私有静态模式integerPattern = Pattern.compile(“ [0-9] *”); 并将验证正文替换为:integerPattern.matcher(text).matches();
P. Jowko '18

38

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

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


30

TextInputTextFormatter可用于格式化,转换和限制类型的文本,可以是输入的。

TextFormatter具有可用于拒绝输入的滤波器。我们需要将其设置为拒绝任何不是有效整数的内容。它还有一个转换器,我们需要将其设置为将字符串值转换为整数值,以便以后绑定。

让我们创建一个可重用的过滤器:

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

筛选器可以执行以下三种操作之一:可以返回未经修改的更改以按原样接受它,可以按其认为合适的某种方式更改更改,或者可以返回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);

如果您不需要可重复使用的过滤器,则可以改用这种单层过滤器:

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

21

我不喜欢异常,因此我使用了matchesString-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
有时您会遇到例外,因为nuber可能太大。
schlagi123

1
提示:将正则表达式“ \\ d +”替换为“ \\ d *”。这将允许您从文本输入字段中删除所有字符(使用退格键/删除键清除该字段)。
Guus 2014年

1
将其设置回旧值后,请不要忘记设置插入符号的位置:textField.positionCaret(textField.getLength());
hsson

if (newValue.matches("\\d*") && newValue.getText().length < 5) 如果要在这种情况下将输入限制为4位数,请将第一条件条件更改为:。
卡布奇诺90年

@ Cappuccino90您应该切换语句,因为每次命中长度检查都比解析正则表达式便宜得多
thatsIch 2016年

9

Java SE 8u40开始,对于此类需求,您可以使用“整数Spinner,以允许使用键盘的向上/向下箭头键或提供的向上/向下箭头按钮安全地选择有效的整数。

您还可以定义一个minmax和一个初始值以限制允许的值,并定义一个增量(以步为单位)。

例如

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

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

在此处输入图片说明

旋转器是一个单行文本字段控制,以允许用户从这样的值的有序序列中选择一个数字或者一个对象的值。微调器通常提供一对微小的箭头按钮,用于逐步浏览序列中的元素。键盘的向上箭头/向下箭头键还在元素之间循环。还可以允许用户直接在微调器中键入(合法)值。尽管组合框提供了类似的功能,但有时还是需要微调框,因为它们不需要下拉列表,这些下拉列表会掩盖重要数据,也因为它们允许诸如从最大值到最小值的换行之类的功能(例如,从最大的正整数到0)。

有关Spinner控件的更多详细信息


人们不应该让微调程序没有验证文本输入或失去焦点
geometrikal

7

如果您使用Java 1.8 Lambda,则首选答案可能更小。

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

if子句对于处理0.5d或0.7f之类的输入很重要,这些输入已由Int.parseInt()正确解析,但不应出现在文本字段中。


9
?因为什么时候0.5d是由Integer.parseInt()正确解析的?它将引发java.lang.NumberFormatException:对于输入字符串:“ 0.5d”(符合预期,因为它不是Integer)
Burkhard

4

试试这个简单的代码就可以了。

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

如果要将同一侦听器应用于多个TextField,这是最简单的解决方案:

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

这个为我工作。

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

我想将Evan Knowles答案与TextFormatterJavaFX 8结合起来以帮助我的想法

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

祝你好运;)保持冷静并编写Java代码


2

下面是一个简单的类来处理一些基本的验证TextField,使用TextFormatterJavaFX中8u40介绍

编辑:

(添加了有关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上的代码


2

这是我用的:

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

2

通过此方法,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);
   }

0

几周前我遇到了这个问题。由于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()}
}

它的用法与normal相同TextBox
但还有一个value属性用于存储输入的整数。
当控件失去焦点时,它将验证该值并将其还原(如果无效)。


2
这是什么语言?
Stealth Rabbi 2015年


0

此代码使您的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));
            }
        }
    });

0

即使您尝试复制/粘贴,此代码也对我有效。

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

    }
});

-1

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

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


1
谢谢您的回答!您能解释一下为什么Platform.runLater需要吗?
TuringTux

@TuringTux在最新版本的JavaFX中,如果您不使用Platform.runLater,它将抛出异常
bdshahab

-1

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

在我的情况下,我有针对UI Component部分的处理程序的类。初始化:

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

关键字已同步如果在完成旧的setText之前调用setText,则添加了以防止javafx中可能出现的渲染锁定问题。如果您将很快开始键入错误的字符,则很容易重现。

另一个优点是,您只保留一个模式以进行匹配,并且仅进行回滚。这样做更好,因为您可以轻松地针对不同的消毒模式取消解决方案。


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

这很好用,因为它只允许您输入整数值和十进制值(具有ASCII码46)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.