聊一聊JavaFx中的TextFormatter以及一元操作符UnaryOperator

直击主题:它在JavaFx中可以实现什么效果

它可以格式化输入文本的内容,可以允许输入哪种值,可以规定光标的位置,例如可以实现一个输入框只允许输入数字,

例如textfield表示输入框对象,那么设置格式化内容的话就应该像这样子:textfield.setTextformatter(new TextFormatter<String>(IntegerFilter)), 而其中IntegerFilter就是只允许输入数字的过滤器,它的代码是怎样的呢?

/**
 * Created by cmlanche on 2017/7/10.
 * 整数过滤器
 * 应用:比如使一个输入框只能输入数字
 */
public class IntegerFilter implements UnaryOperator<TextFormatter.Change> {
    private final static Pattern DIGIT_PATTERN = Pattern.compile("\\d*");

    @Override
    public TextFormatter.Change apply(TextFormatter.Change change) {
        return DIGIT_PATTERN.matcher(change.getText()).matches() ? change : null;
    }
}

DIGIT_PATTERN大家都能看出来它是正则表达式,是匹配文本是否是整数的表达式。

apply方法中的实现的意思是,只要符合整数就返回change,否则返回null

经过测试发现,当返回change的时候,可以允许输入,如输入0~9中的任意数字都可以输入,但输入非数字的话,会返回null,此时发现输入框光标不会移动,而且内容也不会变化,说明null是禁用的意思。

还有个细节就是当按下delete键时,change对象中有个方法叫isDeleted返回true,而文本是空,当按下其他字符,如1时,change中的getText为1,change还有个getControl和getControlText可以返回控件和控件的文本。说明change是包含了当前变化的内容和不变的内容。

那么这个TextFormatter就厉害了,利用change中的信息,可以实现对输入框的各种格式需求,上面例子中让输入框只能输入数字只是TextFormatter的冰山一角,还可以实现各种各样的其他需求,例如让输入框中的值只能是浮点数,只能是字母,字符数只能是6位。有了它,都可以不用对它的值再进行额外的校验,而且可以通用起来,只需要编写不同的过滤器就可以了。

源码解析:TextFormatter是如何发挥作用的?

从上面的分析我们可以清楚的看到是TextFormatter中的filter发挥了过滤作用,而TextFormatter是给TextField使用的,那么TextFormatter必定有函数给TextField来调用,所以我们找到了getFilter,我们在TextField中找这个函数可以看到:

    /**
     * Replaces a range of characters with the given text.
     *
     * @param start The starting index in the range, inclusive. This must be &gt;= 0 and &lt; the end.
     * @param end The ending index in the range, exclusive. This is one-past the last character to
     *            delete (consistent with the String manipulation methods). This must be &gt; the start,
     *            and &lt;= the length of the text.
     * @param text The text that is to replace the range. This must not be null.
     */
    public void replaceText(final int start, final int end, final String text) {
        if (start > end) {
            throw new IllegalArgumentException();
        }

        if (text == null) {
            throw new NullPointerException();
        }

        if (start < 0
            || end > getLength()) {
            throw new IndexOutOfBoundsException();
        }

        if (!this.text.isBound()) {
            final int oldLength = getLength();
            TextFormatter<?> formatter = getTextFormatter();
            TextFormatter.Change change = new TextFormatter.Change(this, getFormatterAccessor(), start, end, text);
            if (formatter != null && formatter.getFilter() != null) {
                change = formatter.getFilter().apply(change);
                if (change == null) {
                    return;
                }
            }

            // Update the content
            updateContent(change, oldLength == 0);

        }
    }
    /**
     * Positions the anchor and caretPosition explicitly.
     */
    public void selectRange(int anchor, int caretPosition) {
        caretPosition = Utils.clamp(0, caretPosition, getLength());
        anchor = Utils.clamp(0, anchor, getLength());

        TextFormatter.Change change = new TextFormatter.Change(this, getFormatterAccessor(), anchor, caretPosition);
        TextFormatter<?> formatter = getTextFormatter();
        if (formatter != null && formatter.getFilter() != null) {
            change = formatter.getFilter().apply(change);
            if (change == null) {
                return;
            }
        }

        updateContent(change, false);
    }
    private boolean filterAndSet(String value) {
        // Send the new value through the textFormatter, if one exists.
        TextFormatter<?> formatter = getTextFormatter();
        int length = content.length();
        if (formatter != null && formatter.getFilter() != null && !text.isBound()) {
            TextFormatter.Change change = new TextFormatter.Change(
                    TextInputControl.this, getFormatterAccessor(), 0, length, value, 0, 0);
            change = formatter.getFilter().apply(change);
            if (change == null) {
                return false;
            }
            replaceText(change.start, change.end, change.text, change.getAnchor(), change.getCaretPosition());
        } else {
            replaceText(0, length, value, 0, 0);
        }
        return true;
    }

如上,从TextField源码中我们找到了三个与TextFormatter的filter有关的方法,他们的大致意思就是当有变化产生时(例如按下字符1),就会触发一个change产生,然后就会调用filter来产生一个新的change对象,这个对象会改变最终输入框中的内容。

陌生知识:UnaryOperator

和大家一样,平时很少看到这个类,我百度查了一下,这个类叫一元运算符,它继承自java.util.function.Function,是jdk中的内容,不是javafx的(包括UnaryOperator也是jdk的内容),源码是这样的:

/**
 * Represents an operation on a single operand that produces a result of the
 * same type as its operand.  This is a specialization of {@code Function} for
 * the case where the operand and result are of the same type.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #apply(Object)}.
 *
 * @param <T> the type of the operand and result of the operator
 *
 * @see Function
 * @since 1.8
 */
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {

    /**
     * Returns a unary operator that always returns its input argument.
     *
     * @param <T> the type of the input and output of the operator
     * @return a unary operator that always returns its input argument
     */
    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}

它的意思是输入和输出是同一个值,函数identity的意思是总是返回输入的参数,而且只有一个参数,这个一元操作符UnaryOperator被注解@FuntionalInterface了,它是java.lang.包中的内容,代码如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

已经涉及到很深的内容了,而identity我猜测应该是与jdk内部实现有关的,会被自动调用的,所有关于他们就到此为止,不再深入分析。

而我注意到一点,一元操作符UnaryOperator有更优雅的用法。文章开头我们注意到IntegerFilter,我们的写法是让它实现UnaryOperator,但其实可以这么做:

textfield.setTextFormatter(new TextFormatter<String>((change)->{
    Pattern DIGIT_PATTERN = Pattern.compile("\\d*");
    return DIGIT_PATTERN.matcher(change.getText()).matches() ? change : null;
}))

为啥变化这么大?

慢慢分析发现对lambda知识的了解的欠缺,接下来的内容涉及到java函数式编程lambda表达式的核心内容,敬请下篇文章更新(^▽^)

转载请注明出处:https://www.cmlanche.com/