【问题标题】:JavaFX TextField validation for integer input and also allow either K or k(for Thousand) or M or m(for Million) in lastJavaFX TextField 验证整数输入,最后允许 K 或 k(千)或 M 或 m(百万)
【发布时间】:2016-03-09 06:20:59
【问题描述】:

我想在 javafx TextField 中添加验证,这样用户应该只能插入整数值([0-9] 和 Dot )。用户还应该能够插入 B 或 b(十亿)和 K 或 k(千)和 M 或 m(百万)。基本上它应该是一个数量字段。 Delete 和 Backspace 也应该可以工作。

例如:

用户点击 k 后,10k 应立即变为 10,000.00,并且 K 不应显示在数量字段(文本字段)上 同样 10M 或 10m 应该转换成 10,000,000.00

不应允许在文本字段中输入 adsadi342fn3 或 31233123werwer 或 dsad342134k。

在 Swing 的情况下,我在验证 TextField 时使用了 getKeyChar 方法。但是对于没有 getKeyChar 方法的 JavaFx,我需要相同的实现。

我使用了以下方法,但问题是它允许用户输入任何值。示例:sdafewr23rf

private void amountEntered() {
        if (amountField != null) {
            String value;
            char[] charArray = amountField.getText().toCharArray();
            if (charArray.length > 0)
                switch (charArray[charArray.length - 1]) {
                    case 't':
                    case 'T':
                        value = multiplyValue(amountField.getText(), new BigDecimal(1000000000000.0));
                        updateAmount(value);
                        break;
                    case 'b':
                    case 'B':
                        value = multiplyValue(amountField.getText(), new BigDecimal(1000000000.0));
                        updateAmount(value);
                        break;
                    case 'm':
                    case 'M':
                        value = multiplyValue(amountField.getText(), new BigDecimal(1000000.0));
                        updateAmount(value);
                        break;
                    case 'k':
                    case 'K':
                        value = multiplyValue(amountField.getText(), new BigDecimal(1000.0));
                        updateAmount(value);
                        break;
                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                    case '.':
                    case ',':
                        updateAmount(amountField.getText());
                        break;
                    default:
                        break;
                }
        }
    }

private String multiplyValue(String number, BigDecimal multValue) {
        //get rid of "," for double parsing
        BigDecimal value = new BigDecimal(cleanDouble(number.substring(0, number.length() - 1)));
        value = value.multiply(multValue);
        return value.toPlainString();
    }

【问题讨论】:

标签: input javafx textfield restrict


【解决方案1】:

除了监听文本属性中的更改并在它们无效时恢复,您还可以使用TextFormatter 否决对文本的更改。使用这种方法将避免textProperty 的其他侦听器看到无效值,然后看到它恢复到以前的值:即textProperty 将始终包含有效的内容。

TextFormatter 采用UnaryOperator<TextFormatter.Change> 作为过滤器。过滤器可以返回 null 以完全否决更改,也可以根据需要修改 Change 的属性。

这是一个相当简单的示例,其中“k”或“K”被“000”替换,“m”或“M”被“000000”替换,其他非数字字符被删除:

import java.util.function.UnaryOperator;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class TextFieldFilteringExample extends Application {

    @Override
    public void start(Stage primaryStage) {
        TextField textField = new TextField();

        textField.textProperty().addListener((obs, oldValue, newValue) -> {
           System.out.println("Text change from "+oldValue+" to "+newValue);
        });

        UnaryOperator<Change> filter = change -> {
            if (change.isAdded()) {
                String addedText = change.getText();
                if (addedText.matches("[0-9]*")) {
                    return change ;
                }
                // remove illegal characters:
                int length = addedText.length();
                addedText = addedText.replaceAll("[^0-9kKmM]", "");
                // replace "k" and "K" with "000":
                addedText = addedText.replaceAll("[kK]", "000");
                // replace "m" and "M" with "000000":
                addedText = addedText.replaceAll("[mM]", "000000");
                change.setText(addedText);

                // modify caret position if size of text changed:
                int delta = addedText.length() - length ;
                change.setCaretPosition(change.getCaretPosition() + delta);  
                change.setAnchor(change.getAnchor() + delta);
            }
            return change ;
        };

        textField.setTextFormatter(new TextFormatter<String>(filter));

        StackPane root = new StackPane(textField);
        root.setPadding(new Insets(20));
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

您还可以修改文本以引入分组分隔符(例如 1,000,000),尽管那里的逻辑相当棘手。您还可以为文本格式化程序指定 StringConverter&lt;BigInteger&gt;,以便格式化程序本身具有 BigInteger 类型的值,这是通过提供的转换器传递文本的结果。

【讨论】:

    【解决方案2】:

    您可以收听text property 中的更改以检查有效输入。就我个人而言,我更喜欢用户能够输入任何字符串并且在用户提交编辑之前不会阻止任何编辑。

    以下示例仅适用于BigInteger(为简单起见),它允许任何以非零开头的数字,后面只能跟数字或用, 分隔的3 位数字。如果输入无效,它会添加 CSS 类 invalid,并在用户按下回车时将其转换为仅包含数字的字符串:

    // regex for matching the input and extracting the parts
    private static final Pattern NUMBER_PATTERN = Pattern.compile("([1-9](?:\\d*|\\d{0,2}(?:\\,\\d{3})*))([tbmk]?)", Pattern.CASE_INSENSITIVE);
    
    // map from suffix to exponent for 10
    private static final Map<Character, Byte> SUFFIX_EXPONENTS;
    
    static {
        Map<Character, Byte> prefixes = new HashMap<>();
        prefixes.put('k', (byte) 3);
        prefixes.put('m', (byte) 6);
        prefixes.put('b', (byte) 9);
        prefixes.put('t', (byte) 12);
        SUFFIX_EXPONENTS = Collections.unmodifiableMap(prefixes);
    }
    
    private static BigInteger convert(String s) {
        if (s == null) {
            return null;
        }
    
        Matcher m = NUMBER_PATTERN.matcher(s);
    
        if (!m.matches()) {
            return null;
        }
    
        String numberString = m.group(1).replace(",", "");
        String suffix = m.group(2);
    
        BigInteger factor = suffix.isEmpty() ? BigInteger.ONE : BigInteger.TEN.pow(SUFFIX_EXPONENTS.get(Character.toLowerCase(suffix.charAt(0))));
    
        return new BigInteger(numberString).multiply(factor);
    }
    
    @Override
    public void start(Stage primaryStage) throws Exception {
        TextField tf = new TextField();
        tf.getStyleClass().add("invalid");
    
        // property bound to the current number in the TextField or null, if invalid
        ObjectProperty<BigInteger> numberProperty = new SimpleObjectProperty<>();
    
        // Binding reevaluated on every change of the text property.
        // A listener could be used instead to change the text to the
        // previous value, if the new input is invalid.
        numberProperty.bind(Bindings.createObjectBinding(() -> convert(tf.getText()), tf.textProperty()));
    
        // change styleclass, if the string becomes (in)valid input
        numberProperty.addListener((observable, oldValue, newValue) -> {
            if (oldValue == null) {
                tf.getStyleClass().remove("invalid");
            } else if (newValue == null) {
                tf.getStyleClass().add("invalid");
            }
        });
    
        // handle user pressing enter
        tf.setOnAction(evt -> {
            BigInteger num = numberProperty.get();
            tf.setText(num == null ? null : num.toString());
        });
    
        Pane root = new StackPane(tf);
    
        Scene sc = new Scene(root, 300, 300);
    
        sc.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
    
        primaryStage.setScene(sc);
        primaryStage.show();
    }
    

    在样式表中,我将无效文本字段的背景设置为红色,以便为用户提供视觉反馈:

    .text-field.invalid {
        -fx-background-color: #f55;
    }
    

    如果您想阻止用户输入任何无法通过附加字符成为有效字符串的内容,您可以删除 numberProperty 以及与之相关的所有内容,并添加一个恢复为旧值的侦听器:

    tf.textProperty().addListener((observable, oldValue, newValue) -> {
        if (isInvalid(newValue)) {
            tf.setText(oldValue);
        }
    });
    

    【讨论】:

      【解决方案3】:

      我创建了以下类来过滤TextField 上的输入,它还使用了JavaFX 8 中引入的TextFormatter

      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("maxCountOf") int maxCountOf) {
              this(modus.createPattern(maxCountOf));
          }
      
          public TextFieldValidator(@NamedArg("regex") String regex){
              this(Pattern.compile(regex));
          }
      
          public TextFieldValidator(Pattern pattern){ 
              INPUT_PATTERN = pattern;
          }
      
          public static TextFieldValidator maxFractionDigits(int maxCountOf) {
              return new TextFieldValidator(maxFractionPattern(maxCountOf));
          }
      
          public static TextFieldValidator maxIntegers(int maxCountOf) {
              return new TextFieldValidator(maxIntegerPattern(maxCountOf));
          }
      
          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 maxCountOf) {
              return Pattern.compile("\\d*(\\" + DECIMAL_SEPARATOR + "\\d{0," + maxCountOf+ "})?");
          }
      
          private static Pattern maxCurrencyFractionPattern(int maxCountOf) {
              return Pattern.compile("^\\" + CURRENCY_SYMBOL + "?\\s?\\d*(\\" + DECIMAL_SEPARATOR + "\\d{0," + maxCountOf+ "})?\\s?\\" +
                      CURRENCY_SYMBOL + "?");
          }
      
          private static Pattern maxIntegerPattern(int maxCountOf) {
              return Pattern.compile("\\d{0," + maxCountOf+ "}");
          }
      
          private static Pattern integersOnlyPattern() {
              return Pattern.compile("\\d*");
          }
      
          public enum ValidationModus {
      
              MAX_CURRENCY_FRACTION_DIGITS {
                  @Override
                  public Pattern createPattern(int maxCountOf) {
                      return maxCurrencyFractionPattern(maxCountOf);
                  }
              },
      
              MAX_FRACTION_DIGITS {
                  @Override
                  public Pattern createPattern(int maxCountOf) {
                      return maxFractionPattern(maxCountOf);
                  }
              },
              MAX_INTEGERS {
                  @Override
                  public Pattern createPattern(int maxCountOf) {
                      return maxIntegerPattern(maxCountOf);
                  }
              },
      
              INTEGERS_ONLY {
                  @Override
                  public Pattern createPattern(int maxCountOf) {
                      return integersOnlyPattern();
                  }
              };
      
              public abstract Pattern createPattern(int maxCountOf);
          }
      }
      

      你可以这样使用它:

      textField.setTextFormatter(new TextFieldValidator(ValidationModus.MAX_INTEGERS, 4).getFormatter());
      

      或者您可以在 fxml 文件中对其进行实例化,并将其应用于具有相应属性的 customTextField。

      app.fxml:

      <fx:define>
          <TextFieldValidator fx:id="validator" modus="MAX_INTEGERS" maxCountOf="4"/>
      </fx:define>
      
      <CustomTextField validator="$validator" />
      

      自定义文本字段:

      public class CustomTextField {
      
      private TextField textField;
      
      public CustomTextField(@NamedArg("validator") TextFieldValidator validator) {
              this();
              textField.setTextFormatter(validator.getFormatter());
          }
      }
      

      对于您的用例,您可以使用适当的正则表达式模式调用 TextFieldValidor 构造函数,并将 James-D 的答案过滤器添加到 validateChange(Change c)

      【讨论】:

        猜你喜欢
        • 2023-04-05
        • 1970-01-01
        • 2019-10-11
        • 1970-01-01
        • 2021-11-23
        • 1970-01-01
        • 2019-11-28
        • 1970-01-01
        相关资源
        最近更新 更多