Подтвердить что ты не робот

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

Ответ 3

Обновление Apr 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 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()));
      }
    });
  }
}

Ответ 4

Начиная с 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);

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

Ответ 5

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

Ответ 6

Мне не нравятся исключения, поэтому я использовал функцию 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);
        }
    }
});

Ответ 7

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(), но не должны отображаться в текстовом поле.

Ответ 8

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

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

Например

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

Пример результата с помощью "целочисленного" счетчика и "двойного" счетчика

введите описание изображения здесь

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

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

Ответ 9

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

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

Ответ 10

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

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

Ответ 11

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

Ответ 12

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

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

Ответ 13

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

Ответ 14

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

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

Ответ 15

Вот простой класс, который обрабатывает некоторые основные проверки на TextField, используя TextFormatter, введенный в JavaFX 8u40

EDIT:

(Код добавлен в отношении комментария 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

Ответ 16

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

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

    }
});

Ответ 17

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

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

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

Ответ 18

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

Ответ 19

Этот код Сделайте свой текстовый фильтр только числом

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 */
            if( c > '9' || c < '0'){
                /** if it not number then just setText to previous one */
                textField.setText(textField.getText().substring(0,textField.getText().length()-1));
            }
        }
    });

Ответ 20

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

Хорошая идея также установить позицию каретки.

Ответ 21

Я хотел бы улучшить ответ Эвана Ноулза: fooobar.com/questions/106034/...

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

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

Ключевое слово синхронизировано добавлено, чтобы предотвратить возможную блокировку блокировки в javafx, если setText будет вызван до того, как старое завершено. Легко воспроизвести, если вы начнете быстро набирать неправильные символы.

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

Ответ 22

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