Проверка JavaFX TextField для целочисленного ввода, а также позволяет либо K, либо k (для тысячи), либо M, либо m (для миллиона) в последнем

Я хочу добавить проверку в javafx TextField, чтобы пользователь мог вставлять только целочисленные значения ([0-9] и Dot ). Также пользователь должен иметь возможность вставить B или b (для миллиарда) и K или k (для тысячи) и M или m (для миллиона). В основном это должно быть поле суммы. Удалить и Backspace также должны работать.

Например :

10k должно стать 10 000,00, как только пользователь нажмет k, и K не должен отображаться в поле суммы (текстовое поле). Точно так же 10M или 10m должны быть преобразованы в 10 000 000,00.

adsadi342fn3 или 31233123werwer или dsad342134k нельзя вводить в текстовое поле.

Я использовал метод getKeyChar при проверке TextField в случае Swing. Но мне нужна такая же реализация в случае JavaFx, где у нас нет метода getKeyChar.

Я использовал следующий метод, но проблема в том, что он позволяет пользователю вводить любое значение. пример: 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();
    }

person Vivek Rawat    schedule 09.03.2016    source источник
comment
Аналогичный (хотя и немного другой) вопрос: Строка с цифрами и буквами для двойного javafx   -  person jewelsea    schedule 10.03.2016


Ответы (3)


Помимо прослушивания изменений в текстовом свойстве и отмены, если они недействительны, вы можете использовать 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<BigInteger> для модуля форматирования текста, чтобы сам модуль форматирования имел значение типа BigInteger, которое является результатом передачи текста через прилагаемый преобразователь.

person James_D    schedule 09.03.2016

Вы можете прослушивать изменения в свойстве text. для проверки правильности ввода. Лично я предпочитаю, чтобы пользователь мог вводить любую строку и не запрещал какие-либо изменения до тех пор, пока пользователь не зафиксирует редактирование.

Следующий пример предназначен только для 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);
    }
});
person fabian    schedule 09.03.2016

Я создал следующий класс для фильтрации ввода на TextField, который также использует TextFormatter, представленный в JavaFX 8.

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 с соответствующими свойствами.

приложение.fxml:

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

<CustomTextField validator="$validator" />

CustomTextField:

public class CustomTextField {

private TextField textField;

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

Для вашего варианта использования вы можете вызвать конструктор TextFieldValidor с соответствующим шаблоном регулярного выражения и добавить фильтр ответа Джеймса-Д на validateChange(Change c)

person jns    schedule 02.04.2016