Posted by & filed under programming.


JavaOne Tokyo 2012でJavaFXに出会ってから,JavaFXの勉強にはまってます.

ちょうど研究室用の購買部会計システムをSwingで作成中だったので,それをJavaFXで書きかえつつ勉強を進めているという感じです.

で,購買部用のシステムということで,JANを入力するTextFieldがあるのですが,JAN専用なので数字しか入力させたくありません.また,JANは13桁と決まっているので13桁以上を入力させたくもありません.

こういったとき,SwingではJFormattedTextFieldを使えば一発なのですが,現在JavaFXではそのようなクラスは存在しませんし,TextFieldのプロパティで指定することも出来ません.

はじめは#onKeyPressedとか#onKeyTypedを使ってどうにかしようとしてましたが,どうもこれらのイベントでは入力を中断させることはできなさそうです.できそうなことといえば,#lastIndexOfでインデックスを取得して削除する,とかでしょうか.

試行錯誤しつつ色々探していると,とても参考になる記事を見つけたのでそれを参考にしつつ今回の要求に沿った独自クラスを実装してみました.

Restricting Input on a TextField // JavaFX News, Demos and Insight // FX Experience

[12/04/12 16:15追記] なお,動作確認環境はJavaSE7u3 + JavaFX 2.1b19です.JavaFX 2.0では動かない…かもしれないです

よく仕組みはわかっていないのですが(おい)実際にこのとおりにしてみると思ったように動いてくれました.TextInputControlが内部的にはキー入力ごとに毎回#replaceTextを呼んでるってことなんでしょう(きっと).

この記事の例では文字種のチェックしかしていないので,文字列長のチェックも含めて作ったクラスが以下です.

package javafx;

import java.util.regex.Pattern;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;

/**
 *
 * @author freiheit
 */
public class RestrictedTextField extends javafx.scene.control.TextField {
    private IntegerProperty maxLength;
    public void setMaxLength(int value) { maxLengthProperty().set(value); }
    public int getMaxLength() { return maxLengthProperty().get(); }
    public IntegerProperty maxLengthProperty() {
        if (maxLength == null) maxLength = new SimpleIntegerProperty(this, "Maximum Length", -1);
        return maxLength;
    }

    private ObjectProperty<Pattern> verifier;
    public void setVerifier(Pattern value) { verifierProperty().set(value); }
    public Pattern getVerifier() { return verifierProperty().get(); }
    public ObjectProperty<Pattern> verifierProperty() {
        if (verifier == null) verifier = new SimpleObjectProperty(this, "Verifier");
        return verifier;
    }

    public static final Pattern NUMBER_ONLY;
    public static final Pattern ALPHABET_ONLY;
    static {
        NUMBER_ONLY     = Pattern.compile("[0-9]");
        ALPHABET_ONLY   = Pattern.compile("[a-z]", Pattern.CASE_INSENSITIVE);
    }

    @Override
    public void replaceText(int start, int end, String text) {
        // If the replaced text would end up being invalid, then simply
        // ignore this call!
        if (text.equals("")) {
            super.replaceText(start, end, text);
        } else {
            if (getMaxLength() > 0 && getLength() < getMaxLength()) {
                if (getVerifier().matcher(text).find()) {
                    super.replaceText(start, end, text);
                }
            }
        }
    }

    @Override
    public void replaceSelection(String text) {
        if (text.equals("")) {
            super.replaceSelection(text);
        } else {
            if (getMaxLength() > 0 && getLength() < getMaxLength()) {
                if (getVerifier().matcher(text).find()) {
                    super.replaceSelection(text);
                }
            }
        }
    }
}

使い方は基本的にはTextFieldのままです.入力文字種や文字数に制限を加えたい時は,verifierPropertyとmaxLengthPropertyにそれぞれ値をいれてやります.

RestrictedTextField text = new RestrictedTextField();
text.setVerifier(Pattern.compile("[0-9a-zA-Z]"));
text.setMaxLength(20);

よく使いそうなパターンについては定数定義してみました.

RestrictedTextField.NUMBER_ONLY   // 数字だけ
RestrictedTextField.ALPHABET_ONLY // アルファベットだけ

今のところ不具合は確認できてませんが,何か改善点等あればコメントをいただけると嬉しいです.