LoginSignup
6
7

More than 5 years have passed since last update.

JavaFX / TextFormatterを使用する

Posted at

環境

  • JDK 1.8.0_121
  • JavaFX Scene Builder 8.3.0

TextFormatterとは

TextFormatter<T>はテキスト入力コントロールにおける「変換と変更」を取り扱うクラスで、TextFieldTextAreaといったテキスト入力コントロールにセットして使います。以下の役割を持ちます。

  • Tのデータモデルを保持する
  • テキスト表現と本来の型Tの相互変換を扱う
  • テキスト入力の状態変更であるChangeを加工する(フィルター)

データモデルの保持

TextFormatterは型引数Tで指定した型のデータモデルを保持します。

getValue()で取り出し、valueProperty()でプロパティを取得できるのでプロパティバインディングも行えます。また、このvalueとテキスト入力コントロールのtextはどちらを書き換えても相互に追従して変わります。

StringConverter

例えば、Integer型のデータをTextFieldで編集しようと思えば一旦テキスト表現に変換して表示する必要があります。また、編集後の値をIntegerとして扱うにはテキストのパースをする必要があります。StringConverterはそういった型の相互変換の枠組みを提供するインタフェースです。

JavaFXは一般的な型用にビルトインStringConverterを既に用意してくれているのでここから選んで使用することができます。
パッケージ javafx.util.converter

フィルター

TextFormatter.Change

TextFormatter.Changeはテキスト入力コントロールの状態変更を保持するクラスです。テキスト入力コントロールが操作される度Changeが生成されてTextFormatterに渡されます。以下の情報を持ちます。

  • 変更前後の選択範囲(キャレットとアンカーの位置)
  • 変更で取り除かれるテキストの範囲
  • 変更後に差し込まれるテキスト
  • 変更の種類(追加、削除、置き換え)
  • 変更前後の全体テキスト

TextFormatter.Change (JavaFX): API Document

フィルターとは

TextFormatterはこのChangeを加工する機能を持ちます。つまり、Changeの気に入らない変更を他の変更に差し替えることができます。TextFormatterではこの操作をフィルターと呼んでおり、Changeを受け取りChangeを返す関数型インタフェースとして実装することが出来ます。

UnaryOperator<TextFormatter.Change> filter;

選択範囲について

選択範囲はアンカーとキャレットの間、と定義されています。キャレットとは入力を行う現在位置のことですが、これが選択範囲の最初にある場合と最後にある場合では意味合いが異なるので注意が必要です。

a.png

TextFormatterの使用例

TextFormatterはStringConverter,フィルターのいずれか、もしくは両方を渡して作成します。
TextFormatter (JavaFX): API Document

以下サンプルです。

日本円の通貨を編集するテキストフィールド

Numberを通貨として表示するビルトインクラスCurrencyStringConverterを使用してみます。

TextFormatter<Number> currencyFormatter = new TextFormatter<>(
        new CurrencyStringConverter(Locale.JAPAN), 
        0, 
        change -> {
    // 選択範囲は1文字目より前にできない
    change.setAnchor(Math.max(1, change.getAnchor()));
    change.setCaretPosition(Math.max(1, change.getCaretPosition()));
    // テキスト変更範囲は1文字目より前にできない
    change.setRange(Math.max(1, change.getRangeStart()), Math.max(1, change.getRangeEnd()));
    return change;
});
textField1.setTextFormatter(currencyFormatter);

b.png

「¥」が常に先頭に表示されています。エンターキーが押された時にCurrencyStringConverterは "¥100000" → 100000 → "¥100,000" といった相互変換を行っており、100000がTextFormattervalueにセットされます。

valueのおかげでテキストフィールドの表現とデータモデルを切り離して取り扱うことができます。以下は数値が30000以下の場合に文字を赤くする例です。

currencyFormatter.valueProperty().addListener((o, oldValue, newValue) -> {
    textField1.setStyle(newValue.intValue() <= 30000 ? 
            "-fx-text-fill: red" : "-fx-text-fill: black");
});

9.png

また、このTextFormatterにはフィルターを渡しており、1文字目である「¥」を選択、削除、変更することが出来なくしてあります。以下はCtrl-Aを押して全選択したところです。

c.png

数字しか入力できないテキストフィールド

Pattern notNumberPattern = Pattern.compile("[^0-9]+");
TextFormatter<String> lowerFormatter = new TextFormatter<>(change -> {
    String newStr = notNumberPattern.matcher(change.getText()).replaceAll("");
    int diffcount = change.getText().length() - newStr.length();
    change.setAnchor(change.getAnchor() - diffcount);
    change.setCaretPosition(change.getCaretPosition() - diffcount);
    change.setText(newStr);
    return change;
});
textField2.setTextFormatter(lowerFormatter);

このTextFormatterはフィルターのみ渡されており、変更差分テキストを毎回数字のみに置き換える仕組みになっています。以下は「2017-04-01T12:00:00」と入力した後のテキストフィールドです。

e.png

フィルターは更新ごとに変換を行うので、キー入力だけでなくコピー&ペーストやTextField::setTextによる値設定でも機能します。

IPv4アドレスを編集するテキストフィールド

1バイトごとに数値入力可能なIPv4アドレスフィールドを作ってみました。数字以外を編集することが出来ず、「.」をタイプすると一つ右のバイトを選択します。

f.png

Inet4AddressFormatter.java

textField3.setTextFormatter(new Inet4AddressFormatter(null));

変更差分からユーザーのマウス操作やキー操作を判断するのには限界があります。手の込んだものを作りたい場合は別のアプローチも検討したほうがよさそうです。

TextFormatterの別の使い道

フィルターはテキストフィールドの変更部分だけを一括で知ることができて便利なので、テキストフィールドのChangeListenerの代わりとして手軽に使われることも多いようです。

参考

ゆっちのBlog: JDK8u40 で追加された Formatted Text も試してみた。
ソース: GitHub
FXLM: GitHub

6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7