Каков рекомендуемый способ создания числового 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:

См. Также другие ответы на этот вопрос, в которых конкретно упоминается 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()));
      }
    });
  }
}
Jewelsea
источник
42

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

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" является последний отредактированный пользователем символ, а не вся строка в текстовом поле, поэтому регулярное выражение может не совпадать правильно. Например, я использую эту команду так: 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);

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

Уве
источник
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 05
1
Совет: замените регулярное выражение «\\ d +» на «\\ d *». Это позволит вам удалить все символы из поля ввода текста (используйте backspace / delete, чтобы очистить поле).
Guus
1
Не забудьте установить положение курсора после того, как вернули его к старому значению:textField.positionCaret(textField.getLength());
hsson
Измените первое условие if на: if (newValue.matches("\\d*") && newValue.getText().length < 5) если вы хотите ограничить ввод до 4 цифр в этом случае.
Cappuccino90
@ 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.

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);
Javasuns
источник
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

jns
источник
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

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

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

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

Александр Кныга
источник
-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).

ShubhamWanne
источник