JavaFXで数値のTextFieldを作成するための推奨される方法は何ですか?


85

TextFieldへの入力を整数に制限する必要があります。何かアドバイス?


3
注:textPropertyを聞くのは間違っています!これは、TextFormatterを使用する前に実行できる最善の方法でした(fx8u40以降)。それ以来、基本的なルールが適用されます。一般に、このメソッドで観測値を変更することは悪い習慣と見なされTextFormatterを使用することが唯一の許容可能な解決策です。
クレオパトラ

回答:


120

非常に古いスレッドですが、これはすっきりしているようで、貼り付けると数字以外の文字が削除されます。

// 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 2017年

5
さらに簡単なのは、テキストをoldValueに戻すことです。そうすれば、正規表現の置き換えをいじくり回す必要はありません(残念ながら私にはまったく機能しませんでした)。
geisterfurz007 2017

@ geisterfurz007これは、貼り付けられたテキストから非数値を取り除くオプションを提供します。
Evan Knowles 2017

10
古い、以下のTextFormatterの回答を参照してください。
gerardw 2018

3
また、単に使用Integer.parseInt(newValue)して使用trycatch、エラーを見つけることもできますNumberFormatException
Pixel

44

2016年4月更新

この回答は数年前に作成されたものであり、元の回答は現在ほとんど廃止されています。

Java 8u40以降、JavaにはTextFormatterがあります。これは通常、JavaFXTextFieldsで数値などの特定の形式の入力を強制するのに最適です。

TextFormatterに特に言及しているこの質問に対する他の回答も参照してください。


元の回答

この要点にはこれのいくつかの例があります、私は以下の例の1つを複製しました:

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

43

私はこれがかなり古いスレッドであることを知っていますが、将来の読者のために、私が非常に直感的に見つけた別の解決策があります:

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
10進数に使用する正規表現(123.456など)を知っている人はいますか?validate(text)関数パラメーター "text"の値は、ユーザーが編集した最後の文字であり、テキストフィールドの文字列全体ではないため、正規表現が正しく一致しない場合があります。たとえば、私はこのコマンドを次のように使用 text.matches("\\d+"); しています。テキストフィールドの文字を削除できません
mils


1
これは良い解決策だと思いますが、1つ変更します。text.matches( "[0-9] *")を書く代わりに、パターンの変数を作成してコンパイルします。コードパターンでは、誰かがフィールドに文字を書き込むたびにコンパイルされますが、これはCPUとメモリにコストがかかります。したがって、変数を追加します。privatestatic Pattern integerPattern = Pattern.compile( "[0-9] *"); そして、検証本文を次のように置き換えます。integerPattern.matcher(text).matches();
P. Jowko 2018年

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

フィルタは、3つのことのいずれかを実行できます。変更を変更せずに返してそのまま受け入れるか、適切と見なされる方法で変更を変更するか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
nuberが大きすぎる可能性があるため、例外がある場合があります。
schlagi123 2014

1
ヒント:正規表現「\\ d +」を「\\ d *」に置き換えます。これにより、テキスト入力フィールドからすべての文字を削除できます(バックスペース/削除を使用してフィールドをクリアします)。
Guus

1
古い値に戻した後、キャレットの位置を設定することを忘れないでください:textField.positionCaret(textField.getLength());
hsson 2015年

if (newValue.matches("\\d*") && newValue.getText().length < 5) この場合、入力を4桁に制限する場合は、fist if条件を:に変更します。
cappuccino90 2016年

@ Cappuccino90正規表現を解析するよりも、ヒットごとに長さのチェックがはるかに安価であるため、ステートメントを切り替える必要があります
thatsIch

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

整数」スピナーと「二重」スピナーを使用した結果の例

ここに画像の説明を入力してください

スピナーは、ユーザがこのような値の順序付けられたシーケンスから数またはオブジェクトを選択できる単一行テキストフィールド制御です。スピナーは通常、シーケンスの要素をステップスルーするための1対の小さな矢印ボタンを提供します。キーボードの上矢印/下矢印キーも要素を循環します。ユーザーは、(正当な)値をスピナーに直接入力することもできます。コンボボックスは同様の機能を提供しますが、重要なデータを隠す可能性のあるドロップダウンリストを必要とせず、最大値から最小値に折り返すなどの機能を使用できるため、スピナーが好まれる場合があります(例:最大の正の整数から0まで)。

スピナーコントロールの詳細


人々はスピナーは、テキスト入力や、失われたフォーカスに検証しないべきではない
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句は、Int.parseInt()によって正しく解析される0.5dや0.7fなどの入力を処理するために重要ですが、テキストフィールドには表示されません。


9
うわー?0.5dはいつInteger.parseInt()によって正しく解析されるのですか?!java.lang.NumberFormatExceptionをスローします。入力文字列の場合: "0.5d"(整数ではないため、予想どおり)
Burkhard

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

私はエヴァンノウルズ答えを組み合わせることから、私の考えで助けたいTextFormatterのJavaFXから8

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

頑張ってください;)落ち着いてJavaをコーディングしてください


2

これは、JavaFX 8u40で導入されたものTextFieldを使用して、のいくつかの基本的な検証を処理する単純なクラスです。TextFormatter

編集:

(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のコード


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
これは何語ですか?
ステルスラビ

これはJavaFXScript Rabbiであり、廃止されました
jewelsea 2016

0

このコードは、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

このコードは、コピー/貼り付けを試みても問題なく機能します。

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 2017年

最近のバージョンのJavaFXでは@TuringTuxで、Platform.runLaterを使用しない場合は例外がスローされます
bdshahab 2017年

-1

Evan Knowlesの回答を改善したい: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);
    }
}

キーワード同期古いものの実行が完了する前にsetTextが呼び出された場合に、javafxで発生する可能性のあるレンダーロックの問題を防ぐために、が追加されました。間違った文字を非常に速く入力し始めると、簡単に再現できます。

もう1つの利点は、一致するパターンを1つだけ保持し、ロールバックを実行することです。さまざまな消毒パターンのソリューションを簡単に抽象化できるため、より優れています。


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

これは、整数値と10進値(ASCIIコード46)のみを入力できるため、正常に機能します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.