7

I am utilizing a few JFormattedTextFields in my program. For some reason when the text field gains focus after a click on the text field, the caret position always jumps to the left (position 0). I would like the caret to end up at the location clicked by the user. So if I click in between two digits, the caret should end up in between those two digits.

So I implemented a FocusListener that would get the click location and set the caret position there.

FocusListener focusListener = new FocusListener(){


    public void focusGained(FocusEvent evt) {

        JFormettedTextField jftf = (JFormattedTextField) evt.getSource();

        //This is where the caret needs to be.
        int dot = jftf.getCaret().getDot(); 

        SwingUtilities.invokeLater( new Runnable() {

        public void run() {
'the textField that has focus'.setCaretPosition('Some how get the evt or dot');              
              }
           });
        }

    public void focusLost (FocusEvent evt) {}

    });

I've tried a number of things to get his to work. I've tried using the final keyword, which works, but only for a single textfield.

I've used set/get methods inside the focus listener to assign the current object, but am not sure about how to make this "safe" (e.g. do they need to be synchronized?).

Maybe there is something I am missing?

Jonas
  • 121,568
  • 97
  • 310
  • 388
SharpBarb
  • 1,590
  • 3
  • 16
  • 40

4 Answers4

13

You need to use a MouseListener:

MouseListener ml = new MouseAdapter()
{
    public void mousePressed(final MouseEvent e)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                JTextField tf = (JTextField)e.getSource();
                int offset = tf.viewToModel(e.getPoint());
                tf.setCaretPosition(offset);
            }
        });
    }
};

formattedTextField.addMouseListener(ml);
camickr
  • 321,443
  • 19
  • 166
  • 288
  • Good answer! But why do you need to do it in invokeLater()? Isn't mousePressed() invoked from the Event-thread anyway? – Jonas Feb 04 '10 at 20:42
  • 2
    @Sanoj, The delay introduced by `invokeLater` is necessary for it to work. Normally when the field is clicked it gains focus, which causes the formatter to re-format the value and update the field text. A side effect of that is that the caret is moved. With `invokeLater`, this `run()` method does not execute until the focus event handling has completed, so you know that once you put the caret in the right place it will stay there. – finnw Feb 04 '10 at 21:10
  • This solution doesn't work if the `JFormattedTextField` is used in a `TableCellEditor` but finnw's solutions does. – Jonas Dec 22 '11 at 13:00
  • @Jonas actually, it does (assuming your comment is related to your recent question http://stackoverflow.com/questions/8598504/how-can-i-get-the-component-at-the-mouse-click-position-when-using-a-tablecelle) - you have to either re-dispatch a converted (to the coordinates of the textField) MouseEvent or manually do the setting in the //do stuff part of my answer there :-) – kleopatra Dec 22 '11 at 13:49
  • @kleopatra: it **doesn't** without your "hack" while finnw's solution works great **without** modifications. – Jonas Dec 22 '11 at 14:37
  • @Jonas hmm ... what do you mean by "hack"? BTW, yeah, I like the formatter approach, too :-) Though it might need a bit more polish to be more generally useabl. – kleopatra Dec 22 '11 at 14:43
  • This solution introduces another small issue. It breaks the selection handling of a text field. Double click selects a word and triple click the whole text. With this mouse event solution, the selections do not work. – Jan Bodnar Sep 27 '14 at 14:40
7

This actually happens in AbstractFormatter.install(JFormattedTextField), which is called when the field gains focus.

I'm not sure why it is designed this way, but you can override this behaviour (as long as your formatter does not change the length of the string in the field.)

Example (assuming the field value is an int):

class IntFormatter extends AbstractFormatter {
    @Override
    public void install(final JFormattedTextField ftf) {
        int prevLen = ftf.getDocument().getLength();
        int savedCaretPos = ftf.getCaretPosition();
        super.install(ftf);
        if (ftf.getDocument().getLength() == prevLen) {
            ftf.setCaretPosition(savedCaretPos);
        }
    }

    public Object stringToValue(String text) throws ParseException {
        return Integer.parseInt(text);
    }

    public String valueToString(Object value) throws ParseException {
        return Integer.toString(((Number) value).intValue());
    }
}

Note that this is not the same as the default Integer formatter. The default formatter uses a DecimalFormat that separates groups of digits, e.g. "1,000,000". This makes the task harder as it changes the length of the string.

finnw
  • 47,861
  • 24
  • 143
  • 221
1

Improved on finnw's solution a bit I think. Example:

public static void main(String[] args) {
    NumberFormat format = NumberFormat.getInstance();
    NumberFormatter formatter = new NumberFormatter(format) {
        @Override
        public void install(JFormattedTextField pField) {
            final JFormattedTextField oldField = getFormattedTextField();
            final int oldLength = pField.getDocument().getLength();
            final int oldPosition = pField.getCaretPosition();

            super.install(pField);

            if (oldField == pField && oldLength == pField.getDocument().getLength()) {
                pField.setCaretPosition(oldPosition);
            }
        }
    };
    JFormattedTextField field = new JFormattedTextField(formatter);
    field.setValue(1234567890);

    JOptionPane.showMessageDialog(null, field);
}
Peter Tseng
  • 13,613
  • 4
  • 67
  • 57
0

If you cannot or do not want to override the Formatter:

    import java.awt.event.FocusEvent;
    import java.awt.event.FocusListener;
    import java.text.NumberFormat;
    
    import javax.swing.JFormattedTextField;
    import javax.swing.JOptionPane;
    import javax.swing.SwingUtilities;
    import javax.swing.text.NumberFormatter;
    
    public class TestJFormattedTextFieldFocus {
    
        public static void main(String[] args) {
            NumberFormat format = NumberFormat.getInstance();
            NumberFormatter formatter = new NumberFormatter(format);
            JFormattedTextField field = new JFormattedTextField(formatter);
            field.addFocusListener(new FocusListener() {
    
                @Override
                public void focusLost(FocusEvent e) {
                }
    
                @Override
                public void focusGained(FocusEvent e) {
                    restoreCaretPosition();
                }
    
                private void restoreCaretPosition() {
                    int caretPosition = field.getCaretPosition();
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            field.setCaretPosition(caretPosition);
                        }
                    });
                }
            });
            field.setValue(1234567890);
            JOptionPane.showMessageDialog(null, field);
        }
    
    }