developer tip

JavaFX에서 숫자 TextField를 만드는 데 권장되는 방법은 무엇입니까?

copycodes 2020. 10. 15. 07:58
반응형

JavaFX에서 숫자 TextField를 만드는 데 권장되는 방법은 무엇입니까?


TextField에 대한 입력을 정수로 제한해야합니다. 어떤 충고?


아주 오래된 스레드이지만 붙여 넣으면 더 깔끔해 보이고 숫자가 아닌 문자가 제거됩니다.

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

나는 이것이 다소 오래된 스레드라는 것을 알고 있지만 미래의 독자를 위해 여기에 매우 직관적 인 또 다른 솔루션이 있습니다.

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 에게 감사드립니다 .


2016 년 4 월 업데이트

이 답변은 몇 년 전에 만들어졌으며 원래 답변은 현재 거의 사용되지 않습니다.

Java 8u40부터 Java에는 일반적으로 JavaFX TextFields에 숫자와 같은 특정 형식의 입력을 적용하는 데 가장 적합한 TextFormatter 가 있습니다.

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 (!"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()));
      }
    });
  }
}

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 속성에 변경 리스너를 추가하고 해당 리스너에서 텍스트를 수정할 때 발생하는 하위 클래스 지정 및 중복 변경 이벤트를 모두 방지 할 수 있습니다.


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

나는 예외를 좋아하지 않아서 matchesString-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);
        }
    }
});

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

" integer "스피너와 " double "스피너 가있는 결과의 예

enter image description here

스피너 사용자는 값들의 순서화 된 시퀀스에서 다수의 또는 객체를 선택할 수 있도록 한 줄의 텍스트 필드 제어이다. 스피너는 일반적으로 시퀀스 요소를 단계별로 이동할 수있는 한 쌍의 작은 화살표 버튼을 제공합니다. 키보드의 위쪽 화살표 / 아래쪽 화살표 키도 요소를 순환합니다. 사용자는 스피너에 직접 (법적) 값을 입력 할 수도 있습니다. 콤보 상자가 유사한 기능을 제공하지만 스피너는 중요한 데이터를 가릴 수있는 드롭 다운 목록이 필요하지 않고 최대 값에서 최소값으로 다시 래핑하는 등의 기능을 허용하기 때문에 선호되는 경우가 있습니다 (예 : 가장 큰 양의 정수에서 0까지).

Spinner 컨트롤 에 대한 자세한 정보


Java 1.8 Lambda를 사용하면 선호하는 답변이 더 작을 수 있습니다.

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

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절은 Int.parseInt ()에 의해 올바르게 구문 분석 된 0.5d 또는 0.7f와 같은 입력을 처리하는 데 중요하지만 텍스트 필드에는 나타나지 않아야합니다.


이 간단한 코드를 시도해보십시오.

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

둘 이상의 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);

이것은 나를 위해 일했습니다.

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

이것이 내가 사용하는 것입니다.

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

이 메서드를 사용하면 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);
   }

다음은 JavaFX 8u40에 도입 된을 TextField사용하여 에서 몇 가지 기본 유효성 검사를 처리하는 간단한 클래스입니다.TextFormatter

편집하다:

(플로 른의 의견에 대한 코드 추가)

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의 코드


이 코드는 복사 / 붙여 넣기를 시도하더라도 잘 작동합니다.

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

    }
});

Evan Knowles 답변을 Java FX 8의 텍스트 포매터와 결합하여 내 아이디어를 돕고 싶습니다.

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

행운을 빕니다;) 침착하고 자바 코드를 작성하십시오


음. 몇 주 전에 그 문제가 발생했습니다. 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()}
}

It's used the same way that a normal TextBox,
but has also a value attribute which stores the entered integer.
When the control looses the focus, it validates the value and reverts it (if isn't valid).


this Code Make your textField Accept only Number

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

In recent updates of JavaFX, you have to set new text in Platform.runLater method just like this:

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

It's a good idea to set caret position too.


I would like to improve Evan Knowles answer: https://stackoverflow.com/a/30796829/2628125

In my case I had class with handlers for UI Component part. Initialization:

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

And the numbericSanitization method:

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

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

Keyword synchronized is added to prevent possible render lock issue in javafx if setText will be called before old one is finished execution. It is easy to reproduce if you will start typing wrong chars really fast.

Another advantage is that you keep only one pattern to match and just do rollback. It is better because you can easily abstragate solution for different sanitization patterns.


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

This works fine as it allows you to enter only integer value and decimal value (having ASCII code 46).

참고URL : https://stackoverflow.com/questions/7555564/what-is-the-recommended-way-to-make-a-numeric-textfield-in-javafx

반응형