JFormattedTextField 货币格式化程序在插入数字后输入两个 0

JFormattedTextField currency formatter enters two 0 after inserting number

在这个 GIF 中,您可以看到错误的地方:

此 JFormattedTextField 和 JFormatter 的代码如下所示:

    NumberFormat format = NumberFormat.getCurrencyInstance(Locale.GERMANY);
    format.setMaximumFractionDigits(2);
    NumberFormatter formatter = new NumberFormatter(format);
    formatter.setAllowsInvalid(false);
    formatter.setOverwriteMode(true);
    JFormattedTextField price = new JFormattedTextField(formatter);
    price.setValue(0.00);

每次数字到达分隔符时,它都会添加两个额外的零。 我怎样才能防止这种情况发生?

您正在覆盖小数点分隔符。当包含的文本是 1,00 €,对应于 1.0,而你用 1 覆盖 ,,得到的文本首先是 1100 €,它将被解析为1100.0 因为 NumberFormat 可以容忍缺少或错位的分组分隔符,以及缺少小数部分。然后数字 1100.0 被重新格式化为 1.100,00 €.

当您继续输入时会重复。

逗号被覆盖的事实似乎是一个错误。我在 NumberFormatter:

中找到了以下代码
/**
 * Subclassed to treat the decimal separator, grouping separator,
 * exponent symbol, percent, permille, currency and sign as literals.
 */
boolean isLiteral(Map<?, ?> attrs) {
    if (!super.isLiteral(attrs)) {
        if (attrs == null) {
            return false;
        }
        int size = attrs.size();

        if (attrs.get(NumberFormat.Field.GROUPING_SEPARATOR) != null) {
            size--;
            if (attrs.get(NumberFormat.Field.INTEGER) != null) {
                size--;
            }
        }
        if (attrs.get(NumberFormat.Field.EXPONENT_SYMBOL) != null) {
            size--;
        }
        if (attrs.get(NumberFormat.Field.PERCENT) != null) {
            size--;
        }
        if (attrs.get(NumberFormat.Field.PERMILLE) != null) {
            size--;
        }
        if (attrs.get(NumberFormat.Field.CURRENCY) != null) {
            size--;
        }
        if (attrs.get(NumberFormat.Field.SIGN) != null) {
            size--;
        }
        return size == 0;
    }
    return true;
}

与提到“小数分隔符”的文档注释枚举相反,字段NumberFormat.Field.DECIMAL_SEPARATOR不在方法内处理。

由于这是一个包私有方法,与大多数实现一样,无法从应用程序更改此行为。我们可以在传入的 NumberFormat 端放置一个变通方法,方法是创建一个自定义的 NumberFormat,它已经在字符迭代器中删除了 DECIMAL_SEPARATOR 的标记。

首先,我们添加一个辅助方法

/**
 * Converts DECIMAL_SEPARATOR fields to literal text.
 */
static AttributedCharacterIterator
    convertSeparatorFieldToLiteral(AttributedCharacterIterator aci) {

    if(aci.current() == CharacterIterator.DONE) return aci;
    int o = aci.getBeginIndex();
    StringBuilder sb = new StringBuilder(aci.getEndIndex()-o);
    do sb.append(aci.current()); while(aci.next() != CharacterIterator.DONE);
    AttributedString s = new AttributedString(sb.toString());
    for(aci.first(); aci.current() != AttributedCharacterIterator.DONE;
        aci.setIndex(aci.getRunLimit())) {

        Map<AttributedCharacterIterator.Attribute, Object> attr = aci.getAttributes();
        if(attr == null || attr.isEmpty()) continue;
        if(attr.containsKey(NumberFormat.Field.DECIMAL_SEPARATOR)) {
            if(attr.size() == 1) continue;
            attr = new HashMap<>(attr);
            attr.remove(NumberFormat.Field.DECIMAL_SEPARATOR);
        }
        s.addAttributes(attr, aci.getRunStart()-o, aci.getRunLimit()-o);
    }
    return s.getIterator();
}

然后,将示例用法更改为

NumberFormat format = NumberFormat.getCurrencyInstance(Locale.GERMANY);
format.setMaximumFractionDigits(2);
NumberFormat myFormat = new NumberFormat() {
    @Override
    public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition p) {
        return format.format(number, toAppendTo, p);
    }
    @Override
    public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
        return format.format(number, toAppendTo, pos);
    }
    @Override
    public Number parse(String source, ParsePosition parsePosition) {
        return format.parse(source, parsePosition);
    }
    @Override
    public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
        AttributedCharacterIterator aci = format.formatToCharacterIterator(obj);
        return convertSeparatorFieldToLiteral(aci);
    }
};

NumberFormatter formatter = new NumberFormatter(myFormat);

formatter.setAllowsInvalid(false);
formatter.setOverwriteMode(true);
JFormattedTextField price = new JFormattedTextField(formatter);
price.setValue(0.00);

自定义 NumberFormat 几乎在所有方面都代表了原始格式。唯一的区别是 DECIMAL_SEPARATOR 属性从字符迭代器中删除,因此小数分隔符将被视为格式的文字文本,无法更改。

因此,您可以在覆盖模式下直接键入。然而,在覆盖模式下处理 JFormattedTextField 仍然很棘手。您需要 setMinimumIntegerDigits(…) 以便更轻松地编写更多位数的数字,但删除小数位将变得不直观。