4

Consider a JFormattedTextField (or any JTextComponent, really) wherein there is a prefix and a suffix displayed around what is the actual "text" of the field.

For instance, the double 3.5 would be the String (via formatting) "3.50" around which would be the prefix "$ " and the suffix "", for a display text of "$ 3.50".

Clearly, this is simple to do. However, the user is still allowed to select text within the prefix/suffix, so they could conceivably delete part or all of the prefix/suffix. I would prefer the user be restricted such that the prefix/suffix cannot be selected at all (while still part of the text field, so no JLabels). I can almost accomplish this with a CaretListener (or by overriding setCaretPosition/moveCaretPosition), which prevents a C-a from selecting the entire field, and it prevents using the arrow keys to move into the prefix/suffix. However, mouse dragging and shift-arrow keys still allows the selection to move into these restricted areas.

Any ideas?

kleopatra
  • 51,061
  • 28
  • 99
  • 211
Chris J
  • 43
  • 4

1 Answers1

7

You can use a NavigationFilter for this.

Here is an example to get you started:

import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;

public class NavigationFilterPrefixWithBackspace extends NavigationFilter
{
    private int prefixLength;
    private Action deletePrevious;

    public NavigationFilterPrefixWithBackspace(int prefixLength, JTextComponent component)
    {
        this.prefixLength = prefixLength;
        deletePrevious = component.getActionMap().get("delete-previous");
        component.getActionMap().put("delete-previous", new BackspaceAction());
        component.setCaretPosition(prefixLength);
    }

    public void setDot(NavigationFilter.FilterBypass fb, int dot, Position.Bias bias)
    {
        fb.setDot(Math.max(dot, prefixLength), bias);
    }

    public void moveDot(NavigationFilter.FilterBypass fb, int dot, Position.Bias bias)
    {
        fb.moveDot(Math.max(dot, prefixLength), bias);
    }

    class BackspaceAction extends AbstractAction
    {
        public void actionPerformed(ActionEvent e)
        {
            JTextComponent component = (JTextComponent)e.getSource();

            if (component.getCaretPosition() > prefixLength)
            {
                deletePrevious.actionPerformed( null );
            }
        }
    }

    public static void main(String args[]) throws Exception {

        JTextField textField = new JTextField("Prefix_", 20);
        textField.setNavigationFilter( new NavigationFilterPrefixWithBackspace(7, textField) );

        JFrame frame = new JFrame("Navigation Filter Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(textField);
        frame.pack();
        frame.setLocationRelativeTo( null );
        frame.setVisible(true);
    }
}

I believe this is a how a JFormattedTextField works. So I'm not sure if you can use this with a formatted text field as is may replace the default behaviour.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • I'd like to thank you for your answer, NavigationFilters are clearly the correct solution. Regarding the formatted text field, an AbstractFormatter has an overrideable getNavigationFilter() which will apply one of these to the field. That would be exactly what I need. – Chris J Sep 20 '11 at 14:01