views:

123

answers:

2

I'm using a NumberFormatter and JFormattedTextField, but the .getValue() doesn't return the same value as the user is seeing.

I think the input-string is parsed using NumberFormats parse-method, and I get the Numberformat from NumberFormat.getNumberInstance(); with the actual Locale. So I don't think I easily can extend it and write my own parse-method?

In example, if the user types 1234.487 the getValue() will return: 1234.487 but the user will be displayed 1,234.49

Another example, using NumberFormat.getCurrencyInstance();. The user types 1234.487 and the getValue() will return 1234.487 but the user will be displayed $1,234.49

Instead, I want an ParseException be generated if the Formatter can't format the value without rounding. Same thing if the user types 4.35b6, by default the formatter will display 4.35 and the value will be 4.35, but I want a ParseException, because the user typed in an invalid value.

Here is the code that I have tried with:

NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMaximumFractionDigits(2);
nf.setMinimumFractionDigits(2);
final JFormattedTextField ftf = new JFormattedTextField(nf);
ftf.setValue(new BigDecimal("1234.50"));

// Print the value from ftf
final JTextField txt = new JTextField(12);
txt.addFocusListener(new FocusAdapter() {
    public void focusGained(FocusEvent e) {
        txt.setText(ftf.getValue().toString());
    }
});

How to get the same value as the user is seeing?

+2  A: 

Try this:

txt.setText(ftf.getFormatter().valueToString(ftf.getValue()));

You'll also have to handle the java.text.ParseException that the valueToString method throws.

Edit: The formatter can be set to disallow invalid entries altogether, which might help:

    ((DefaultFormatter)ftf.getFormatter()).setAllowsInvalid( false );
Ash
That doesn't work. You then have to parse the string again, because the string contains formatting, like currencysymbol and grouping of the digits. And I don't think there is a parser for strings with currencysymbols?
Jonas
When I run your code (with the above change) and type "1234.487" I see "1,234.49" in both fields. Isn't that what you wanted?
Ash
No, I want the value, not the String, so I can do calculations. If I wanted it that way I could use getText(). I think that if the user types three fraction digits a ParseException should be generated and the formatter should not do rounding, that's important. If I do like your example, the value has the grouping-symbol between 1 and 2, so I have to parse it again.
Jonas
@Sanoj: Okay, so you want the formatter to tell you (via ParseException) as soon as the user enters something that's invalid?
Ash
This is pretty tricky, given the distinction that the formatter makes between value and text. You can set the default formatter to disallow invalid entries altogether - would that help?
Ash
It was pretty hard to edit the text if .setAllowsInvalid() is set to False, so I will go with finnw's solution instead. Specially if it is a decimal number. Anyway, thanks for the help!
Jonas
Yeah, no problem. I liked finnw's solution too.
Ash
+3  A: 

You should extend NumberFormatter rather than NumberFormat and override stringToValue so that it verifies that when the string is parsed you get back the original value:

class StrictNumberFormatter extends javax.swing.text.NumberFormatter {
    @Override
    public Object stringToValue(String text) throws ParseException {
        Object parsedValue = super.stringToValue(text);
        String expectedText = super.valueToString(parsedValue);
        if (! super.stringToValue(expectedText).equals(parsedValue)) {
            throw new ParseException("Rounding occurred", 0);
        }
        return parsedValue;
    }

    public StrictNumberFormatter(NumberFormat nf) {
        super(nf);
    }
}

NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMaximumFractionDigits(2);
nf.setMinimumFractionDigits(2);
JFormattedTextField.AbstractFormatter formatter = new StrictNumberFormatter(nf);
final JFormattedTextField ftf = new JFormattedTextField(formatter);
ftf.setValue(new BigDecimal("1234.50"));

This formatter will reject the new text if the values before and after rounding do not match.

Note: this compares the values, not the strings. It will tolerate changes like removing grouping characters ("$1,000" => "$1000") and trailing zeros ("$1.20" => "$1.2"). Basically if the NumberFormat returns the same value then it is acceptable. But any restrictions imposed by the NumberFormat still apply, e.g. you must not remove the currency symbol or insert leading spaces etc.

finnw
Will that work in the case of a currency formatter?
Ash
@Ash, yes I just tried it with `getCurrencyInstance()`. It ought to work with any valid `NumberFormat`.
finnw
It was much easier to edit the textfield using this solution, so I will go with this instead. Thanks!
Jonas
I just wanted to thank you for an elegant solution. This problem had me stumped!
Luhar
@Luhar You're welcome :-)
finnw