javaswingjtextfieldcaretjformattedtextfield

JFormattedTextField wrong caret position when i type the first number


I have modified NumberFormatter to have like a currency instance (with prefix). When I write the first number, this application have add the prefix to the number JFormattedTextField Empty but when I do that, the caret position change before the first number like this enter image description here

How can I fix this by only modifying method formato() – that returns a NumberFormatter – to the constructor of JFormattedTextField?

textFieldMonto = new javax.swing.JFormattedTextField(formato());

This is the method:

private NumberFormatter formato() {
    DecimalFormat myFormatter = new DecimalFormat("'Gs. '###,##0;'Gs. '###,##0");

    NumberFormatter numberFormatter = new NumberFormatter(myFormatter) {

        // this change caret to the end in every focus gained
        @Override
        public void install(JFormattedTextField pField) {
            super.install(pField);
            pField.setCaretPosition(pField.getDocument().getLength());
        }

        // allow empty text on JFormattedTextField and dont allow negative numbers
        @Override
        public String valueToString(Object value) throws ParseException {
            String result = super.valueToString(value);
            if(super.valueToString(value).startsWith("-"))
                result = result.replaceFirst("-", "");  // this block every negative number
            if(value==null)
                return "";
            return result;
        }

        // allow empty text on JFormattedTextField and dont allow negative numbers
        @Override
        public Object stringToValue(String text) throws ParseException {
            if (text.length() == 0 || text.equals("Gs. ")) // if is empty or only have the prefix, return null
                return null;
            text.replaceFirst("-", ""); // this block every negative number
            if(!text.startsWith("Gs. "))   //if is empty, add the prefix "Gs. " to the number
                text = "Gs. " + text;
            return super.stringToValue(text);
        }
    };
    numberFormatter.setAllowsInvalid(false); //this is the key!!
    numberFormatter.setMaximum(new BigDecimal("999999999999"));// maximum number to put
    numberFormatter.setCommitsOnValidEdit(true);// commit value on each keystroke instead of focus lost
    return numberFormatter;
}

Solution

  • I don't think this comment, in the code in your question, is true:

    // this change caret to the end in every focus gained

    Method install is not called every time the JFormattedTextField gains focus. According to my tests, it is called only when a JFormattedTextField object is created.

    According to the javadoc:

    Subclasses will typically only need to override this if they wish to install additional listeners on the JFormattedTextField.

    Hence I suggest adding a DocumentListener to the JFormattedTextField's document, in method install.

    Note that there are other DocumentListeners that are set up by the Swing infrastructure and since there is no guarantee regarding the order of execution when there is more than one DocumentListener, the implementation, in the below code, uses invokeLater to ensure that the listener that I added is run last.

    Here is my rewrite of your formato method.
    The only thing I changed was method install.

    private NumberFormatter formato() {
        DecimalFormat myFormatter = new DecimalFormat("'Gs. '###,##0;'Gs. '###,##0");
    
        NumberFormatter numberFormatter = new NumberFormatter(myFormatter) {
    
            // this change caret to the end in every focus gained
            @Override
            public void install(JFormattedTextField pField) {
                super.install(pField);
                pField.getDocument().addDocumentListener(new DocumentListener() {
    
                    @Override
                    public void insertUpdate(DocumentEvent e) {
                        EventQueue.invokeLater(() -> pField.setCaretPosition(pField.getDocument().getLength()));
                    }
    
                    @Override
                    public void removeUpdate(DocumentEvent e) {
                        EventQueue.invokeLater(() -> pField.setCaretPosition(pField.getDocument().getLength()));
                    }
    
                    @Override
                    public void changedUpdate(DocumentEvent e) {
                        // Do nothing.
                    }
                });
            }
    
            // allow empty text on JFormattedTextField and don't allow negative numbers
            @Override
            public String valueToString(Object value) throws ParseException {
                String result = super.valueToString(value);
                if(super.valueToString(value).startsWith("-"))
                    result = result.replaceFirst("-", "");  // this block every negative number
                if(value==null)
                    return "";
                return result;
            }
    
            // allow empty text on JFormattedTextField and don't allow negative numbers
            @Override
            public Object stringToValue(String text) throws ParseException {
                if (text.length() == 0 || text.equals("Gs. ")) // if is empty or only have the prefix, return null
                    return null;
                text.replaceFirst("-", ""); // this block every negative number
                if(!text.startsWith("Gs. "))   //if is empty, add the prefix "Gs. " to the number
                    text = "Gs. " + text;
                return super.stringToValue(text);
            }
        };
        numberFormatter.setAllowsInvalid(false); //this is the key!!
        numberFormatter.setMaximum(new BigDecimal("999999999999"));// maximum number to put
        numberFormatter.setCommitsOnValidEdit(true);// commit value on each keystroke instead of focus lost
        return numberFormatter;
    }