環境
- JDK 1.8.0_121
- JavaFX Scene Builder 8.3.0
TextFormatterとは
TextFormatter<T>
はテキスト入力コントロールにおける「変換と変更」を取り扱うクラスで、TextField
やTextArea
といったテキスト入力コントロールにセットして使います。以下の役割を持ちます。
- 型
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;
選択範囲について
選択範囲はアンカーとキャレットの間、と定義されています。キャレットとは入力を行う現在位置のことですが、これが選択範囲の最初にある場合と最後にある場合では意味合いが異なるので注意が必要です。
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);
「¥」が常に先頭に表示されています。エンターキーが押された時にCurrencyStringConverterは "¥100000" → 100000 → "¥100,000" といった相互変換を行っており、100000がTextFormatter
のvalue
にセットされます。
value
のおかげでテキストフィールドの表現とデータモデルを切り離して取り扱うことができます。以下は数値が30000以下の場合に文字を赤くする例です。
currencyFormatter.valueProperty().addListener((o, oldValue, newValue) -> {
textField1.setStyle(newValue.intValue() <= 30000 ?
"-fx-text-fill: red" : "-fx-text-fill: black");
});
また、このTextFormatter
にはフィルターを渡しており、1文字目である「¥」を選択、削除、変更することが出来なくしてあります。以下はCtrl-Aを押して全選択したところです。
数字しか入力できないテキストフィールド
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」と入力した後のテキストフィールドです。
フィルターは更新ごとに変換を行うので、キー入力だけでなくコピー&ペーストやTextField::setText
による値設定でも機能します。
IPv4アドレスを編集するテキストフィールド
1バイトごとに数値入力可能なIPv4アドレスフィールドを作ってみました。数字以外を編集することが出来ず、「.」をタイプすると一つ右のバイトを選択します。
textField3.setTextFormatter(new Inet4AddressFormatter(null));
変更差分からユーザーのマウス操作やキー操作を判断するのには限界があります。手の込んだものを作りたい場合は別のアプローチも検討したほうがよさそうです。
TextFormatterの別の使い道
フィルターはテキストフィールドの変更部分だけを一括で知ることができて便利なので、テキストフィールドのChangeListenerの代わりとして手軽に使われることも多いようです。
参考
ゆっちのBlog: JDK8u40 で追加された Formatted Text も試してみた。
ソース: GitHub
FXLM: GitHub