Який рекомендований спосіб створити числовий TextField у JavaFX?


85

Мені потрібно обмежити введення в TextField цілими числами. Будь-яка порада?


3
Примітка: прослуховування textProperty помилково ! Це було найкраще, що можна було зробити, перш ніж мати TextFormatter (починаючи з fx8u40 або близько того). З тих пір застосовується основне правило: загалом вважається поганою практикою модифікувати спостережуване значення в цьому методі, а використання TextFormatter є єдиним прийнятним рішенням.
kleopatra

Відповіді:


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

3
Можливо, також можна просто використовувати Integer.parseInt(newValue)і використовувати tryі catchвиявити помилку наNumberFormatException
Pixel

43

Оновлення квітня 2016 р

Ця відповідь була створена кілька років тому, і оригінальна відповідь зараз майже застаріла.

Починаючи з Java 8u40, Java має TextFormatter, який, як правило, є найкращим для примусового введення певних форматів, таких як цифри, на JavaFX TextFields:

Дивіться також інші відповіді на це запитання, в яких конкретно згадується 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)? Значення параметра перевірки (тексту) функції "текст" - це останній відредагований користувачем символ, а не весь рядок у текстовому полі, тому регулярний вираз може не відповідати належним чином. Наприклад, я використовую цю команду так: text.matches("\\d+"); і я не можу видалити жодного символу в текстовому полі
mils

1
@mils Подивіться тут і тут .
vellotis

1
Я думаю, що це хороше рішення, але я змінив би одне. Замість того, щоб писати text.matches ("[0-9] *"), я б створив змінну для шаблону та скомпілював її. У вашому коді візерунок компілюється кожного разу, коли хтось пише символ у поле, і це дорого для процесора та пам'яті. Тож я б додав змінну: private static Pattern integerPattern = Pattern.compile ("[0-9] *"); І замінити перевірку тіла на: integerPattern.matcher (text) .matches ();
П. Джовко

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

Це дозволяє уникнути подій як на підкласи, так і на дублікати змін, які ви отримаєте, коли додасте прослуховувач змін до властивості тексту та зміните текст у цьому прослуховувачі.


30

TextInputмаєTextFormatter який можна використовувати для форматування, конвертування та обмеження типів тексту, який можна ввести.

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

Мені не подобаються винятки, тому я використовував matchesфункцію з String-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
іноді у вас є виняток, оскільки кількість може бути занадто великою.
schlagi123

1
Порада: замініть регулярний вираз "\\ d +" на "\\ d *". Це дозволить вам видалити всі символи з поля введення тексту (використовуйте backspace / delete, щоб очистити поле).
Гус,

1
Не забудьте встановити положення каретки після повернення її до старого значення:textField.positionCaret(textField.getLength());
hsson

Змініть кулак, якщо умова, на: if (newValue.matches("\\d*") && newValue.getText().length < 5) якщо ви хочете обмежити введення до 4 цифр у цьому випадку.
Капучино90

@ Cappuccino90 Вам слід змінити оператори, оскільки перевірка довжини набагато дешевша при кожному зверненні, ніж аналіз регулярного
виразу

9

Починаючи з Java SE 8u40 , для таких потреб ви можете використовувати " ціле число ", Spinnerщо дозволяє безпечно вибрати дійсне ціле число, використовуючи клавіші зі стрілками вгору / стрілка вниз або кнопки зі стрілкою вгору / вниз.

Ви також можете визначити мінімальне , максимальне та початкове значення, щоб обмежити дозволені значення та суму, яку потрібно збільшувати або зменшувати, за крок.

Наприклад

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

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
Woot? З якого часу 0,5d правильно аналізується Integer.parseInt () ?! Він викидає java.lang.NumberFormatException: Для вхідного рядка: "0.5d" (як очікувалося, оскільки це не ціле число)
Бурхард,

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

Я хочу допомогти зі своєю ідеєю, поєднавши відповідь Евана Ноулза з TextFormatterJavaFX 8

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

так що удачі;) зберігайте спокій і кодуйте java


2

Ось простий клас, який обробляє деякі основні перевірки TextField, використовуючи TextFormatterвведені в JavaFX 8u40

РЕДАГУВАТИ:

(Код додано щодо коментаря Флоерна)

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

    }
});

Те саме в позначенні лямбда буде:

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

Він використовується так само, як звичайний TextBox,
але має також valueатрибут, який зберігає введене ціле число.
Коли елемент керування втрачає фокус, він перевіряє значення та повертає його (якщо воно не є дійсним).


2
що це за мова?
Stealth Rabbi

Це JavaFX Script Rabbi, він застарів .
jewelsea

0

цей код Зробіть своїм текстовим полем Лише номер

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

Я хотів би покращити відповідь Евана Ноулза: https://stackoverflow.com/a/30796829/2628125

У моєму випадку я мав клас з обробниками для компонента UI Component. Ініціалізація:

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

І метод numicSanitization:

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

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

Ключове слово синхронізовано слово додано, щоб запобігти можливій проблемі блокування візуалізації в javafx, якщо setText буде викликаний до завершення старого. Це легко відтворити, якщо ви почнете дуже швидко друкувати неправильні символи.

Ще одна перевага полягає в тому, що ви зберігаєте лише один зразок, який збігається, і просто робите відкат. Це краще, оскільки ви можете легко абстрагувати рішення для різних схем санітарії.


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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.