it-swarm.com.ru

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

Мне нужно ограничить ввод в TextField целыми числами. Любой совет?

72
Harry Mitchell

Очень старая тема, но она выглядит аккуратнее и удаляет нечисловые символы при вставке.

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

Я знаю, что это довольно старая тема, но для будущих читателей здесь есть другое решение, которое я нашел довольно интуитивным:

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 за предложенные улучшения.

37
Burkhard

Обновление апрель 2016 г.

Этот ответ был создан несколько лет назад, и в настоящее время первоначальный ответ в значительной степени устарел. 

Начиная с Java 8u40, в Java есть TextFormatter , который обычно лучше всего подходит для принудительного ввода определенных форматов, таких как числа, в JavaFX TextFields:

Смотрите также другие ответы на этот вопрос, в которых конкретно упоминается TextFormatter.


Оригинальный ответ

Есть несколько примеров этого в этом Gist , я продублировал один из примеров ниже:

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

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 );
23
Emily L.

Начиная с 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 и измените текст в этом прослушивателе.

22
Uwe

Я не люблю исключения, поэтому я использовал функцию 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);
        }
    }
});
21
thatsIch
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 (), но не должны отображаться в текстовом поле.

6
ubuntudroid

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

Вы также можете определить значение min, max и значение initial, чтобы ограничить допустимые значения и величину, которая увеличивается или уменьшается на каждый шаг.

Например

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

Пример результата со счетчиком "integer" и счетчиком "double"

 enter image description here

Spinner - это однострочный элемент управления текстовым полем, который позволяет пользователю выберите число или значение объекта из упорядоченной последовательности таких ценности. Обычно прядильщики предоставляют пару крошечных кнопок со стрелками для пошаговое прохождение элементов последовательности. Клавиатура вверх клавиши со стрелками/стрелками вниз также переключают элементы. Пользователь может также разрешено вводить (легальное) значение непосредственно в счетчик . Хотя комбинированные блоки предоставляют аналогичную функциональность, прядильщики являются иногда предпочтительнее, потому что им не требуется выпадающий список, который может скрывать важные данные, а также потому, что они позволяют функции такие как перенос с максимального значения обратно на минимальное значение (например, от наибольшего положительного целого до 0).

Подробнее о элемент управления Spinner

6
Nicolas Filotto

Предпочтительный ответ может быть даже меньше, если вы используете Java 1.8 Lambdas

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

Этот работал для меня.

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
Martin

Если вы хотите применить один и тот же слушатель к нескольким 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);
2
javasuns

Попробуйте этот простой код, он сделает свою работу.

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;
    }
}));
2
Hassan Latif

Этот метод позволяет 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);
   }
2
gmatagmis

Это то, что я использую:

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
Aren Isa

Вот простой класс, который обрабатывает некоторые базовые проверки 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

1
jns

Этот код прекрасно работает для меня, даже если вы пытаетесь скопировать/вставить.

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

    }
});
1
Ahmed Jaouadi

я хочу помочь с моей идеей объединить ответ Эвана Ноулза с текстовым форматом из Javafx 8 

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

так что удачи;) сохраняйте спокойствие и код Java

1
Salah Eddine Chaibi

В последних обновлениях 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) {
    }
}

Хорошей идеей является также установка каретки.

0
bdshahab

этот код Сделайте ваш 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
Amine Harbaoui
    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).

0
ShubhamWanne

Я хотел бы улучшить ответ Эвана Ноулза: https://stackoverflow.com/a/30796829/2628125

В моем случае у меня был класс с обработчиками для компонента UI. Инициализация:

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

И метод numbericSanitization:

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

Еще одним преимуществом является то, что вы сохраняете только один шаблон и просто выполняете откат. Это лучше, потому что вы можете легко абстрагировать решение для различных моделей очистки.

0
Oleksandr Knyga

Мммм. Я столкнулся с этой проблемой несколько недель назад. Поскольку 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, в котором хранится введенное целое число.
Когда элемент управления теряет фокус, он проверяет значение и возвращает его (если он недействителен).

0
Alba Mendez